diff --git a/pyproject.toml b/pyproject.toml index ae0a8a47da0..629561c227c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -129,11 +129,7 @@ module = [ 'poetry.mixology.term', 'poetry.mixology.version_solver', 'poetry.repositories.installed_repository', - 'poetry.utils.appdirs', - 'poetry.utils.authenticator', 'poetry.utils.env', - 'poetry.utils.exporter', - 'poetry.utils.password_manager', 'poetry.utils.setup_reader', ] ignore_errors = true diff --git a/src/poetry/utils/appdirs.py b/src/poetry/utils/appdirs.py index ca6bb9a432d..476bd0231fa 100644 --- a/src/poetry/utils/appdirs.py +++ b/src/poetry/utils/appdirs.py @@ -7,17 +7,11 @@ import os import sys -from typing import TYPE_CHECKING - - -if TYPE_CHECKING: - from pathlib import Path - WINDOWS = sys.platform.startswith("win") or (sys.platform == "cli" and os.name == "nt") -def expanduser(path: str | Path) -> str: +def expanduser(path: str) -> str: """ Expand ~ and ~user constructions. @@ -214,14 +208,14 @@ def _get_win_folder_with_ctypes(csidl_name: str) -> str: }[csidl_name] buf = ctypes.create_unicode_buffer(1024) - ctypes.windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf) + ctypes.windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf) # type: ignore[attr-defined] # noqa: E501 # Downgrade to short path name if have highbit chars. See # . has_high_char = any(ord(c) > 255 for c in buf) if has_high_char: buf2 = ctypes.create_unicode_buffer(1024) - if ctypes.windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024): + if ctypes.windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024): # type: ignore[attr-defined] # noqa: E501 buf = buf2 return buf.value diff --git a/src/poetry/utils/authenticator.py b/src/poetry/utils/authenticator.py index a7d0dda3d89..9ceb53b7f90 100644 --- a/src/poetry/utils/authenticator.py +++ b/src/poetry/utils/authenticator.py @@ -19,6 +19,7 @@ from cleo.io.io import IO from poetry.config.config import Config + from poetry.utils.types import Auth logger = logging.getLogger() @@ -28,8 +29,8 @@ class Authenticator: def __init__(self, config: Config, io: IO | None = None) -> None: self._config = config self._io = io - self._session = None - self._credentials = {} + self._session: requests.Session | None = None + self._credentials: dict[str, tuple[str | None, str | None]] = {} self._password_manager = PasswordManager(self._config) def _log(self, message: str, level: str = "debug") -> None: @@ -69,7 +70,7 @@ def request(self, method: str, url: str, **kwargs: Any) -> requests.Response: ) # Send the request. - send_kwargs = { + send_kwargs: Any = { "timeout": kwargs.get("timeout"), "allow_redirects": kwargs.get("allow_redirects", True), } @@ -117,11 +118,14 @@ def get_credentials_for_url(self, url: str) -> tuple[str | None, str | None]: # Split from the left because that's how urllib.parse.urlsplit() # behaves if more than one : is present (which again can be checked # using the password attribute of the return value) - credentials = auth.split(":", 1) if ":" in auth else (auth, None) - credentials = tuple( - None if x is None else urllib.parse.unquote(x) for x in credentials + credentials_tmp = auth.split(":", 1) if ":" in auth else (auth, None) + credentials_tmp = tuple( + None if x is None else urllib.parse.unquote(x) + for x in credentials_tmp ) + credentials = credentials_tmp[0], credentials_tmp[1] + if credentials[0] is not None or credentials[1] is not None: credentials = (credentials[0] or "", credentials[1] or "") @@ -132,10 +136,10 @@ def get_credentials_for_url(self, url: str) -> tuple[str | None, str | None]: def get_pypi_token(self, name: str) -> str: return self._password_manager.get_pypi_token(name) - def get_http_auth(self, name: str) -> dict[str, str] | None: + def get_http_auth(self, name: str) -> Auth | None: return self._get_http_auth(name, None) - def _get_http_auth(self, name: str, netloc: str | None) -> dict[str, str] | None: + def _get_http_auth(self, name: str, netloc: str | None) -> Auth | None: if name == "pypi": url = "https://upload.pypi.org/legacy/" else: @@ -155,6 +159,7 @@ def _get_http_auth(self, name: str, netloc: str | None) -> dict[str, str] | None ) return auth + return None def _get_credentials_for_netloc(self, netloc: str) -> tuple[str | None, str | None]: for repository_name in self._config.get("repositories", []): @@ -169,7 +174,7 @@ def _get_credentials_for_netloc(self, netloc: str) -> tuple[str | None, str | No def _get_credentials_for_netloc_from_keyring( self, url: str, netloc: str, username: str | None - ) -> dict[str, str] | None: + ) -> Auth | None: import keyring cred = keyring.get_credential(url, username) diff --git a/src/poetry/utils/exporter.py b/src/poetry/utils/exporter.py index 147b930fec2..6bced05e129 100644 --- a/src/poetry/utils/exporter.py +++ b/src/poetry/utils/exporter.py @@ -8,15 +8,15 @@ from poetry.core.packages.utils.utils import path_to_url +from poetry.repositories.remote_repository import RemoteRepository from poetry.utils._compat import decode if TYPE_CHECKING: from pathlib import Path - from cleo.io.io import IO - from poetry.poetry import Poetry + from poetry.utils.types import Writer class Exporter: @@ -36,7 +36,7 @@ def export( self, fmt: str, cwd: Path, - output: IO | str, + output: Writer | str, with_hashes: bool = True, dev: bool = False, extras: bool | Sequence[str] | None = None, @@ -59,7 +59,7 @@ def export( def _export_requirements_txt( self, cwd: Path, - output: IO | str, + output: Writer | str, with_hashes: bool = True, dev: bool = False, extras: bool | Sequence[str] | None = None, @@ -143,7 +143,7 @@ def _export_requirements_txt( repositories = [ r for r in self._poetry.pool.repositories - if r.url == index.rstrip("/") + if (isinstance(r, RemoteRepository) and r.url == index.rstrip("/")) ] if not repositories: continue @@ -151,6 +151,7 @@ def _export_requirements_txt( if ( self._poetry.pool.has_default() and repository is self._poetry.pool.repositories[0] + and isinstance(repository, RemoteRepository) ): url = ( repository.authenticated_url @@ -170,13 +171,13 @@ def _export_requirements_txt( content = indexes_header + "\n" + content - self._output(content, cwd, output) + self._write(content, cwd, output) - def _output(self, content: str, cwd: Path, output: IO | str) -> None: + def _write(self, content: str, cwd: Path, output: Writer | str) -> None: decoded = decode(content) - try: - output.write(decoded) - except AttributeError: + if isinstance(output, str): filepath = cwd / output with filepath.open("w", encoding="utf-8") as f: f.write(decoded) + return + output.write(decoded) diff --git a/src/poetry/utils/password_manager.py b/src/poetry/utils/password_manager.py index fcef51d157e..6eb652bfbcb 100644 --- a/src/poetry/utils/password_manager.py +++ b/src/poetry/utils/password_manager.py @@ -8,6 +8,8 @@ if TYPE_CHECKING: from poetry.config.config import Config + from poetry.utils.types import Auth + logger = logging.getLogger(__name__) @@ -121,10 +123,10 @@ def _check(self) -> None: class PasswordManager: def __init__(self, config: Config) -> None: self._config = config - self._keyring = None + self._keyring: KeyRing | None = None @property - def keyring(self) -> KeyRing | None: + def keyring(self) -> KeyRing: if self._keyring is None: self._keyring = KeyRing("poetry-repository") if not self._keyring.is_available(): @@ -141,18 +143,19 @@ def set_pypi_token(self, name: str, token: str) -> None: self.keyring.set_password(name, "__token__", token) def get_pypi_token(self, name: str) -> str: - if not self.keyring.is_available(): - return self._config.get(f"pypi-token.{name}") - - return self.keyring.get_password(name, "__token__") + token = self.keyring.get_password(name, "__token__") + if token is not None: + return token + return self._config.get(f"pypi-token.{name}") def delete_pypi_token(self, name: str) -> None: if not self.keyring.is_available(): - return self._config.auth_config_source.remove_property(f"pypi-token.{name}") + self._config.auth_config_source.remove_property(f"pypi-token.{name}") + return self.keyring.delete_password(name, "__token__") - def get_http_auth(self, name: str) -> dict[str, str] | None: + def get_http_auth(self, name: str) -> Auth | None: auth = self._config.get(f"http-basic.{name}") if not auth: username = self._config.get(f"http-basic.{name}.username") @@ -181,7 +184,7 @@ def set_http_password(self, name: str, username: str, password: str) -> None: def delete_http_password(self, name: str) -> None: auth = self.get_http_auth(name) - if not auth or "username" not in auth: + if auth is None or "username" not in auth: return with suppress(KeyRingError): diff --git a/src/poetry/utils/setup_reader.py b/src/poetry/utils/setup_reader.py index baac42c12ff..dbb9fd104a4 100644 --- a/src/poetry/utils/setup_reader.py +++ b/src/poetry/utils/setup_reader.py @@ -312,6 +312,7 @@ def _find_single_string( if variable is not None and isinstance(variable, ast.Str): return variable.s + return None def _find_in_call(self, call: ast.Call, name: str) -> Any | None: for keyword in call.keywords: @@ -342,8 +343,9 @@ def _find_variable_in_body(self, body: Iterable[Any], name: str) -> Any | None: if target.id == name: return elem.value + return None - def _find_in_dict(self, dict_: ast.Dict | ast.Call, name: str) -> Any | None: + def _find_in_dict(self, dict_: ast.Dict, name: str) -> Any | None: for key, val in zip(dict_.keys, dict_.values): if isinstance(key, ast.Str) and key.s == name: return val diff --git a/src/poetry/utils/types.py b/src/poetry/utils/types.py new file mode 100644 index 00000000000..be38071fd42 --- /dev/null +++ b/src/poetry/utils/types.py @@ -0,0 +1,14 @@ +from __future__ import annotations + +from typing import Protocol +from typing import TypedDict + + +class Auth(TypedDict): + username: str + password: str | None + + +class Writer(Protocol): + def write(self, text: str) -> None: + pass