Commit 3d6ffced by David Hotham Committed by GitHub

more typechecking (#5485)

clean up errors revealed by having a py.typed in poetry-core and running mypy in a proper environment

* more typechecking

* remove remaining `FooTypes` type annotations

just use the appropriate base class, no need for the extra complexity

* typecheck a couple more files

* more typechecking fixes

* more typechecking

* more typechecking

* tweak typing to appease pre-commit

* further mixology fixes

* consistency in repository package-finding

signal failure with `PackageNotFound`, rather than by returning None

* typechecking vcs.git.backend
parent 4d0b710d
......@@ -30,7 +30,8 @@ class FileConfigSource(ConfigSource):
return self._file
def add_property(self, key: str, value: Any) -> None:
with self.secure() as config:
with self.secure() as toml:
config: dict[str, Any] = toml
keys = key.split(".")
for i, key in enumerate(keys):
......@@ -44,7 +45,8 @@ class FileConfigSource(ConfigSource):
config = config[key]
def remove_property(self, key: str) -> None:
with self.secure() as config:
with self.secure() as toml:
config: dict[str, Any] = toml
keys = key.split(".")
current_config = config
......
......@@ -2,10 +2,12 @@ from __future__ import annotations
import contextlib
from typing import Any
from typing import cast
from cleo.helpers import argument
from cleo.helpers import option
from tomlkit.toml_document import TOMLDocument
try:
......@@ -114,7 +116,9 @@ You can specify a package in the following forms:
"You can only specify one package when using the --extras option"
)
content = self.poetry.file.read()
# tomlkit types are awkward to work with, treat content as a mostly untyped
# dictionary.
content: dict[str, Any] = self.poetry.file.read()
poetry_content = content["tool"]["poetry"]
if group == MAIN_GROUP:
......@@ -130,9 +134,10 @@ You can specify a package in the following forms:
groups = poetry_content["group"]
if group not in groups:
group_table = parse_toml(
dependencies_toml: dict[str, Any] = parse_toml(
f"[tool.poetry.group.{group}.dependencies]\n\n"
)["tool"]["poetry"]["group"][group]
)
group_table = dependencies_toml["tool"]["poetry"]["group"][group]
poetry_content["group"][group] = group_table
if "dependencies" not in poetry_content["group"][group]:
......@@ -158,11 +163,13 @@ You can specify a package in the following forms:
)
for _constraint in requirements:
if "version" in _constraint:
version = _constraint.get("version")
if version is not None:
# Validate version constraint
parse_constraint(_constraint["version"])
assert isinstance(version, str)
parse_constraint(version)
constraint = inline_table()
constraint: dict[str, Any] = inline_table()
for name, value in _constraint.items():
if name == "name":
continue
......@@ -210,16 +217,18 @@ You can specify a package in the following forms:
if len(constraint) == 1 and "version" in constraint:
constraint = constraint["version"]
section[_constraint["name"]] = constraint
constraint_name = _constraint["name"]
assert isinstance(constraint_name, str)
section[constraint_name] = constraint
with contextlib.suppress(ValueError):
self.poetry.package.dependency_group(group).remove_dependency(
_constraint["name"]
constraint_name
)
self.poetry.package.add_dependency(
Factory.create_dependency(
_constraint["name"],
constraint_name,
constraint,
groups=[group],
root_dir=self.poetry.file.parent,
......@@ -247,6 +256,7 @@ You can specify a package in the following forms:
status = self._installer.run()
if status == 0 and not self.option("dry-run"):
assert isinstance(content, TOMLDocument)
self.poetry.file.write(content)
return status
......
......@@ -68,6 +68,7 @@ class DebugResolveCommand(InitCommand):
for constraint in requirements:
name = constraint.pop("name")
assert isinstance(name, str)
extras = []
for extra in self.option("extras"):
if " " in extra:
......
......@@ -15,8 +15,7 @@ from poetry.console.commands.env_command import EnvCommand
if TYPE_CHECKING:
from cleo.io.inputs.option import Option
from poetry.packages.project_package import ProjectPackage
from poetry.core.packages.project_package import ProjectPackage
class GroupCommand(EnvCommand):
......
......@@ -2,6 +2,7 @@ from __future__ import annotations
import os
from typing import Any
from typing import cast
from cleo.helpers import argument
......@@ -78,7 +79,7 @@ You can specify a package in the following forms:
# We check for the plugins existence first.
if env_dir.joinpath("pyproject.toml").exists():
pyproject = tomlkit.loads(
pyproject: dict[str, Any] = tomlkit.loads(
env_dir.joinpath("pyproject.toml").read_text(encoding="utf-8")
)
poetry_content = pyproject["tool"]["poetry"]
......@@ -128,8 +129,8 @@ You can specify a package in the following forms:
# We add the plugins to the dependencies section of the previously
# created `pyproject.toml` file
pyproject = PyProjectTOML(env_dir.joinpath("pyproject.toml"))
poetry_content = pyproject.poetry_config
pyproject_toml = PyProjectTOML(env_dir.joinpath("pyproject.toml"))
poetry_content = pyproject_toml.poetry_config
poetry_dependency_section = poetry_content["dependencies"]
plugin_names = []
for plugin in plugins:
......@@ -137,7 +138,7 @@ You can specify a package in the following forms:
# Validate version constraint
parse_constraint(plugin["version"])
constraint = tomlkit.inline_table()
constraint: dict[str, Any] = tomlkit.inline_table()
for name, value in plugin.items():
if name == "name":
continue
......@@ -150,7 +151,7 @@ You can specify a package in the following forms:
poetry_dependency_section[plugin["name"]] = constraint
plugin_names.append(plugin["name"])
pyproject.save()
pyproject_toml.save()
# From this point forward, all the logic will be deferred to
# the update command, by using the previously created `pyproject.toml`
......
from __future__ import annotations
from collections import defaultdict
from typing import TYPE_CHECKING
from typing import DefaultDict
from typing import Any
from poetry.console.commands.command import Command
if TYPE_CHECKING:
from poetry.core.packages.package import Package
class PluginShowCommand(Command):
name = "plugin show"
......@@ -26,7 +21,7 @@ class PluginShowCommand(Command):
from poetry.utils.helpers import canonicalize_name
from poetry.utils.helpers import pluralize
plugins: DefaultDict[str, dict[str, Package | list[str]]] = defaultdict(
plugins: dict[str, dict[str, Any]] = defaultdict(
lambda: {
"package": None,
"plugins": [],
......
......@@ -4,6 +4,7 @@ from typing import Any
from cleo.helpers import argument
from cleo.helpers import option
from tomlkit.toml_document import TOMLDocument
try:
......@@ -50,7 +51,7 @@ list of installed packages
else:
group = self.option("group", self.default_group)
content = self.poetry.file.read()
content: dict[str, Any] = self.poetry.file.read()
poetry_content = content["tool"]["poetry"]
if group is None:
......@@ -114,6 +115,7 @@ list of installed packages
status = self._installer.run()
if not self.option("dry-run") and status == 0:
assert isinstance(content, TOMLDocument)
self.poetry.file.write(content)
return status
......
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import cast
from cleo.helpers import argument
from cleo.helpers import option
from poetry.core.packages.directory_dependency import DirectoryDependency
from poetry.core.packages.file_dependency import FileDependency
from poetry.core.packages.vcs_dependency import VCSDependency
from poetry.console.commands.group_command import GroupCommand
......@@ -12,8 +16,8 @@ if TYPE_CHECKING:
from cleo.io.io import IO
from poetry.core.packages.dependency import Dependency
from poetry.core.packages.package import Package
from poetry.core.packages.project_package import ProjectPackage
from poetry.packages.project_package import ProjectPackage
from poetry.repositories.installed_repository import InstalledRepository
from poetry.repositories.repository import Repository
......@@ -82,10 +86,10 @@ lists all packages available."""
if self.option("tree") and not package:
requires = root.all_requires
packages = locked_repo.packages
for pkg in packages:
for p in packages:
for require in requires:
if pkg.name == require.name:
self.display_package_tree(self._io, pkg, locked_repo)
if p.name == require.name:
self.display_package_tree(self._io, p, locked_repo)
break
return 0
......@@ -383,7 +387,7 @@ lists all packages available."""
def find_latest_package(
self, package: Package, root: ProjectPackage
) -> Package | bool:
) -> Package | None:
from cleo.io.null_io import NullIO
from poetry.puzzle.provider import Provider
......@@ -398,10 +402,13 @@ lists all packages available."""
provider = Provider(root, self.poetry.pool, NullIO())
if dep.is_vcs():
dep = cast(VCSDependency, dep)
return provider.search_for_vcs(dep)[0]
if dep.is_file():
dep = cast(FileDependency, dep)
return provider.search_for_file(dep)[0]
if dep.is_directory():
dep = cast(DirectoryDependency, dep)
return provider.search_for_directory(dep)[0]
name = package.name
......
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import Any
from cleo.helpers import argument
from cleo.helpers import option
from tomlkit.toml_document import TOMLDocument
from poetry.console.commands.command import Command
......@@ -64,10 +66,11 @@ patch, minor, major, prepatch, preminor, premajor, prerelease.
f" to <fg=green>{version}</>"
)
content = self.poetry.file.read()
content: dict[str, Any] = self.poetry.file.read()
poetry_content = content["tool"]["poetry"]
poetry_content["version"] = version.text
assert isinstance(content, TOMLDocument)
self.poetry.file.write(content)
else:
if self.option("short"):
......@@ -100,7 +103,9 @@ patch, minor, major, prepatch, preminor, premajor, prerelease.
new = new.first_prerelease()
elif rule == "prerelease":
if parsed.is_unstable():
new = Version(parsed.epoch, parsed.release, parsed.pre.next())
pre = parsed.pre
assert pre is not None
new = Version(parsed.epoch, parsed.release, pre.next())
else:
new = parsed.next_patch().first_prerelease()
else:
......
......@@ -2,10 +2,14 @@ from __future__ import annotations
from pathlib import Path
from typing import TYPE_CHECKING
from typing import Any
from typing import cast
from cleo.io.null_io import NullIO
from poetry.core.factory import Factory as BaseFactory
from poetry.core.packages.vcs_dependency import VCSDependency
from poetry.core.toml.file import TOMLFile
from tomlkit.toml_document import TOMLDocument
from poetry.config.config import Config
from poetry.config.file_config_source import FileConfigSource
......@@ -183,7 +187,7 @@ class Factory(BaseFactory):
from poetry.layouts.layout import POETRY_DEFAULT
pyproject = tomlkit.loads(POETRY_DEFAULT)
pyproject: dict[str, Any] = tomlkit.loads(POETRY_DEFAULT)
content = pyproject["tool"]["poetry"]
content["name"] = package.name
......@@ -195,8 +199,9 @@ class Factory(BaseFactory):
dependency_section["python"] = package.python_versions
for dep in package.requires:
constraint = tomlkit.inline_table()
constraint: dict[str, Any] = tomlkit.inline_table()
if dep.is_vcs():
dep = cast(VCSDependency, dep)
constraint[dep.vcs] = dep.source_url
if dep.reference:
......@@ -217,6 +222,7 @@ class Factory(BaseFactory):
dependency_section[dep.name] = constraint
assert isinstance(pyproject, TOMLDocument)
path.joinpath("pyproject.toml").write_text(
pyproject.as_string(), encoding="utf-8"
)
......@@ -34,11 +34,10 @@ if TYPE_CHECKING:
from poetry.core.packages.package import Package
from poetry.config.config import Config
from poetry.installation.operations import OperationTypes
from poetry.installation.operations.install import Install
from poetry.installation.operations import Install
from poetry.installation.operations import Uninstall
from poetry.installation.operations import Update
from poetry.installation.operations.operation import Operation
from poetry.installation.operations.uninstall import Uninstall
from poetry.installation.operations.update import Update
from poetry.repositories import Pool
from poetry.utils.env import Env
......@@ -127,7 +126,7 @@ class Executor:
return 0
def execute(self, operations: list[OperationTypes]) -> int:
def execute(self, operations: list[Operation]) -> int:
self._total_operations = len(operations)
for job_type in self._executed:
self._executed[job_type] = 0
......@@ -195,7 +194,7 @@ class Executor:
return default_max_workers
return min(default_max_workers, desired_max_workers)
def _write(self, operation: OperationTypes, line: str) -> None:
def _write(self, operation: Operation, line: str) -> None:
if not self.supports_fancy_output() or not self._should_write_operation(
operation
):
......@@ -213,7 +212,7 @@ class Executor:
section.clear()
section.write(line)
def _execute_operation(self, operation: OperationTypes) -> None:
def _execute_operation(self, operation: Operation) -> None:
try:
op_message = self.get_operation_message(operation)
if self.supports_fancy_output():
......@@ -290,7 +289,7 @@ class Executor:
with self._lock:
self._shutdown = True
def _do_execute_operation(self, operation: OperationTypes) -> int:
def _do_execute_operation(self, operation: Operation) -> int:
method = operation.job_type
operation_message = self.get_operation_message(operation)
......@@ -326,9 +325,7 @@ class Executor:
return result
def _increment_operations_count(
self, operation: OperationTypes, executed: bool
) -> None:
def _increment_operations_count(self, operation: Operation, executed: bool) -> None:
with self._lock:
if executed:
self._executed_operations += 1
......@@ -353,7 +350,7 @@ class Executor:
def get_operation_message(
self,
operation: OperationTypes,
operation: Operation,
done: bool = False,
error: bool = False,
warning: bool = False,
......@@ -401,7 +398,7 @@ class Executor:
)
return ""
def _display_summary(self, operations: list[OperationTypes]) -> None:
def _display_summary(self, operations: list[Operation]) -> None:
installs = 0
updates = 0
uninstalls = 0
......@@ -707,7 +704,7 @@ class Executor:
def _should_write_operation(self, operation: Operation) -> bool:
return not operation.skipped or self._dry_run or self._verbose
def _save_url_reference(self, operation: OperationTypes) -> None:
def _save_url_reference(self, operation: Operation) -> None:
"""
Create and store a PEP-610 `direct_url.json` file, if needed.
"""
......
......@@ -25,7 +25,6 @@ if TYPE_CHECKING:
from poetry.config.config import Config
from poetry.installation.base_installer import BaseInstaller
from poetry.installation.operations import OperationTypes
from poetry.installation.operations.operation import Operation
from poetry.packages import Locker
from poetry.utils.env import Env
......@@ -345,7 +344,7 @@ class Installer:
self._io.write_line("")
self._io.write_line("<info>Writing lock file</>")
def _execute(self, operations: list[OperationTypes]) -> int:
def _execute(self, operations: list[Operation]) -> int:
if self._use_executor:
return self._executor.execute(operations)
......
......@@ -12,7 +12,7 @@ if TYPE_CHECKING:
class NoopInstaller(BaseInstaller):
def __init__(self) -> None:
self._installs: list[Package] = []
self._updates: list[Package] = []
self._updates: list[tuple[Package, Package]] = []
self._removals: list[Package] = []
@property
......@@ -20,7 +20,7 @@ class NoopInstaller(BaseInstaller):
return self._installs
@property
def updates(self) -> list[Package]:
def updates(self) -> list[tuple[Package, Package]]:
return self._updates
@property
......
......@@ -7,4 +7,4 @@ from poetry.installation.operations.uninstall import Uninstall
from poetry.installation.operations.update import Update
OperationTypes = Union[Install, Uninstall, Update]
__all__ = ["Install", "Uninstall", "Update"]
......@@ -2,11 +2,13 @@ from __future__ import annotations
from pathlib import Path
from typing import TYPE_CHECKING
from typing import Any
from tomlkit import dumps
from tomlkit import inline_table
from tomlkit import loads
from tomlkit import table
from tomlkit.toml_document import TOMLDocument
from poetry.utils.helpers import canonicalize_name
from poetry.utils.helpers import module_name
......@@ -115,7 +117,7 @@ class Layout:
def generate_poetry_content(self, original: PyProjectTOML | None = None) -> str:
template = POETRY_DEFAULT
content = loads(template)
content: dict[str, Any] = loads(template)
poetry_content = content["tool"]["poetry"]
poetry_content["name"] = self._project
......@@ -162,14 +164,15 @@ class Layout:
build_system.add("requires", ["poetry-core" + build_system_version])
build_system.add("build-backend", "poetry.core.masonry.api")
assert isinstance(content, TOMLDocument)
content.add("build-system", build_system)
content = dumps(content)
text = dumps(content)
if original and original.file.exists():
content = dumps(original.data) + "\n" + content
text = dumps(original.data) + "\n" + text
return content
return text
def _create_default(self, path: Path, src: bool = True) -> None:
package_path = path / self.package_path
......
......@@ -74,7 +74,7 @@ class EditableBuilder(Builder):
added_files += self._add_scripts()
self._add_dist_info(added_files)
def _run_build_script(self, build_script: Path) -> None:
def _run_build_script(self, build_script: str) -> None:
self._debug(f" - Executing build script: <b>{build_script}</b>")
self._env.run("python", str(self._path.joinpath(build_script)), call=True)
......
......@@ -16,8 +16,8 @@ if TYPE_CHECKING:
def resolve_version(
root: ProjectPackage,
provider: Provider,
locked: dict[str, list[DependencyPackage]] = None,
use_latest: list[str] = None,
locked: dict[str, list[DependencyPackage]] | None = None,
use_latest: list[str] | None = None,
) -> SolverResult:
solver = VersionSolver(root, provider, locked=locked, use_latest=use_latest)
......
......@@ -9,7 +9,7 @@ from poetry.mixology.set_relation import SetRelation
if TYPE_CHECKING:
from poetry.core.packages.dependency import Dependency
from poetry.core.semver.helpers import VersionTypes
from poetry.core.semver.version_constraint import VersionConstraint
class Term:
......@@ -33,7 +33,7 @@ class Term:
return self._dependency
@property
def constraint(self) -> VersionTypes:
def constraint(self) -> VersionConstraint:
return self._dependency.constraint
def is_positive(self) -> bool:
......@@ -159,7 +159,7 @@ class Term:
)
def _non_empty_term(
self, constraint: VersionTypes, is_positive: bool
self, constraint: VersionConstraint, is_positive: bool
) -> Term | None:
if constraint.is_empty():
return None
......
......@@ -22,7 +22,6 @@ from poetry.packages import DependencyPackage
if TYPE_CHECKING:
from poetry.core.packages.package import Package
from poetry.core.packages.project_package import ProjectPackage
from poetry.puzzle.provider import Provider
......@@ -42,7 +41,7 @@ class DependencyCache:
def __init__(self, provider: Provider):
self.provider = provider
self.cache: dict[str, list[Package]] = {}
self.cache: dict[str, list[DependencyPackage]] = {}
@functools.lru_cache(maxsize=128)
def search_for(self, dependency: Dependency) -> list[DependencyPackage]:
......@@ -74,8 +73,8 @@ class VersionSolver:
self,
root: ProjectPackage,
provider: Provider,
locked: dict[str, list[Package]] = None,
use_latest: list[str] = None,
locked: dict[str, list[DependencyPackage]] | None = None,
use_latest: list[str] | None = None,
):
self._root = root
self._provider = provider
......@@ -109,7 +108,7 @@ class VersionSolver:
)
try:
next = self._root.name
next: str | None = self._root.name
while next is not None:
self._propagate(next)
next = self._choose_package_version()
......@@ -450,7 +449,7 @@ class VersionSolver:
)
if not conflict:
self._solution.decide(package)
self._solution.decide(package.package)
self._log(
f"selecting {package.complete_name} ({package.full_pretty_version})"
)
......@@ -494,7 +493,7 @@ class VersionSolver:
locked = self._locked.get(dependency.name, [])
for package in locked:
if (allow_similar or dependency.is_same_package_as(package)) and (
if (allow_similar or dependency.is_same_package_as(package.package)) and (
dependency.constraint.allows(package.version)
or package.is_prerelease()
and dependency.constraint.allows(package.version.next_patch())
......
......@@ -3,3 +3,6 @@ from __future__ import annotations
from poetry.packages.dependency_package import DependencyPackage
from poetry.packages.locker import Locker
from poetry.packages.package_collection import PackageCollection
__all__ = ["DependencyPackage", "Locker", "PackageCollection"]
......@@ -49,7 +49,7 @@ class DependencyPackage:
def __hash__(self) -> int:
return hash(self._package)
def __eq__(self, other: Package | DependencyPackage) -> bool:
def __eq__(self, other: object) -> bool:
if isinstance(other, DependencyPackage):
other = other.package
......
......@@ -9,18 +9,18 @@ from copy import deepcopy
from hashlib import sha256
from pathlib import Path
from typing import TYPE_CHECKING
from typing import Any
from typing import Iterable
from typing import Iterator
from typing import Sequence
from typing import cast
from poetry.core.packages.dependency import Dependency
try:
from poetry.core.packages.dependency_group import MAIN_GROUP
except ImportError:
MAIN_GROUP = "default"
from poetry.core.packages.directory_dependency import DirectoryDependency
from poetry.core.packages.file_dependency import FileDependency
from poetry.core.packages.package import Package
from poetry.core.packages.url_dependency import URLDependency
from poetry.core.packages.vcs_dependency import VCSDependency
from poetry.core.semver.helpers import parse_constraint
from poetry.core.semver.version import Version
from poetry.core.toml.file import TOMLFile
......@@ -31,7 +31,9 @@ from tomlkit import document
from tomlkit import inline_table
from tomlkit import item
from tomlkit import table
from tomlkit.container import Table
from tomlkit.exceptions import TOMLKitError
from tomlkit.items import Array
from poetry.packages import DependencyPackage
from poetry.utils.extras import get_extra_package_names
......@@ -39,7 +41,6 @@ from poetry.utils.extras import get_extra_package_names
if TYPE_CHECKING:
from poetry.core.version.markers import BaseMarker
from tomlkit.items import InlineTable
from tomlkit.toml_document import TOMLDocument
from poetry.repositories import Repository
......@@ -57,7 +58,7 @@ class Locker:
def __init__(self, lock: str | Path, local_config: dict) -> None:
self._lock = TOMLFile(lock)
self._local_config = local_config
self._lock_data = None
self._lock_data: TOMLDocument | None = None
self._content_hash = self._get_content_hash()
@property
......@@ -88,7 +89,7 @@ class Locker:
metadata = lock.get("metadata", {})
if "content-hash" in metadata:
return self._content_hash == lock["metadata"]["content-hash"]
return self._content_hash == metadata["content-hash"]
return False
......@@ -104,7 +105,7 @@ class Locker:
lock_data = self.lock_data
packages = Repository()
locked_packages = lock_data["package"]
locked_packages = cast("list[dict[str, Any]]", lock_data["package"])
if not locked_packages:
return packages
......@@ -127,16 +128,16 @@ class Locker:
)
package.description = info.get("description", "")
package.category = info.get("category", "main")
package.groups = info.get("groups", [MAIN_GROUP])
package.optional = info["optional"]
if "hashes" in lock_data["metadata"]:
metadata = cast("dict[str, Any]", lock_data["metadata"])
name = info["name"]
if "hashes" in metadata:
# Old lock so we create dummy files from the hashes
package.files = [
{"name": h, "hash": h}
for h in lock_data["metadata"]["hashes"][info["name"]]
]
hashes = cast("dict[str, Any]", metadata["hashes"])
package.files = [{"name": h, "hash": h} for h in hashes[name]]
else:
package.files = lock_data["metadata"]["files"][info["name"]]
files = metadata["files"][name]
package.files = files
package.python_versions = info["python-versions"]
extras = info.get("extras", {})
......@@ -182,6 +183,7 @@ class Locker:
if package.source_type == "directory":
# root dir should be the source of the package relative to the lock
# path
assert package.source_url is not None
root_dir = Path(package.source_url)
if isinstance(constraint, list):
......@@ -368,10 +370,10 @@ class Locker:
yield DependencyPackage(dependency=dependency, package=package)
def set_lock_data(self, root: Package, packages: list[Package]) -> bool:
files = table()
packages = self._lock_packages(packages)
files: dict[str, Any] = table()
package_specs = self._lock_packages(packages)
# Retrieving hashes
for package in packages:
for package in package_specs:
if package["name"] not in files:
files[package["name"]] = []
......@@ -383,12 +385,14 @@ class Locker:
files[package["name"]].append(file_metadata)
if files[package["name"]]:
files[package["name"]] = item(files[package["name"]]).multiline(True)
package_files = item(files[package["name"]])
assert isinstance(package_files, Array)
files[package["name"]] = package_files.multiline(True)
del package["files"]
lock = document()
lock["package"] = packages
lock["package"] = package_specs
if root.extras:
lock["extras"] = {
......@@ -445,7 +449,8 @@ class Locker:
except TOMLKitError as e:
raise RuntimeError(f"Unable to read the lock file ({e}).")
lock_version = Version.parse(lock_data["metadata"].get("lock-version", "1.0"))
metadata = cast(Table, lock_data["metadata"])
lock_version = Version.parse(metadata.get("lock-version", "1.0"))
current_version = Version.parse(self._VERSION)
# We expect the locker to be able to read lock files
# from the same semantic versioning range
......@@ -469,7 +474,7 @@ class Locker:
return lock_data
def _lock_packages(self, packages: list[Package]) -> list:
def _lock_packages(self, packages: list[Package]) -> list[dict[str, Any]]:
locked = []
for package in sorted(packages, key=lambda x: (x.name, x.version)):
......@@ -479,22 +484,31 @@ class Locker:
return locked
def _dump_package(self, package: Package) -> dict:
dependencies: dict[str, list[InlineTable]] = {}
def _dump_package(self, package: Package) -> dict[str, Any]:
dependencies: dict[str, list[Any]] = {}
for dependency in sorted(package.requires, key=lambda d: d.name):
if dependency.pretty_name not in dependencies:
dependencies[dependency.pretty_name] = []
constraint = inline_table()
if dependency.is_directory() or dependency.is_file():
if dependency.is_directory():
dependency = cast(DirectoryDependency, dependency)
constraint["path"] = dependency.path.as_posix()
if dependency.is_directory() and dependency.develop:
if dependency.develop:
constraint["develop"] = True
elif dependency.is_file():
dependency = cast(FileDependency, dependency)
constraint["path"] = dependency.path.as_posix()
elif dependency.is_url():
dependency = cast(URLDependency, dependency)
constraint["url"] = dependency.url
elif dependency.is_vcs():
dependency = cast(VCSDependency, dependency)
constraint[dependency.vcs] = dependency.source
if dependency.branch:
......@@ -519,16 +533,16 @@ class Locker:
# All the constraints should have the same type,
# but we want to simplify them if it's possible
for dependency, constraints in tuple(dependencies.items()):
for dependency_name, constraints in dependencies.items():
if all(
len(constraint) == 1 and "version" in constraint
for constraint in constraints
):
dependencies[dependency] = [
dependencies[dependency_name] = [
constraint["version"] for constraint in constraints
]
data = {
data: dict[str, Any] = {
"name": package.pretty_name,
"version": package.pretty_version,
"description": package.description or "",
......
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import Sequence
from poetry.packages.dependency_package import DependencyPackage
......@@ -14,7 +15,7 @@ class PackageCollection(list):
def __init__(
self,
dependency: Dependency,
packages: list[Package | DependencyPackage] = None,
packages: Sequence[Package | DependencyPackage] | None = None,
) -> None:
self._dependency = dependency
......
......@@ -15,10 +15,10 @@ from poetry.core.utils.helpers import normalize_version
from requests import adapters
from requests.exceptions import ConnectionError
from requests.exceptions import HTTPError
from requests.packages.urllib3 import util
from requests_toolbelt import user_agent
from requests_toolbelt.multipart import MultipartEncoder
from requests_toolbelt.multipart import MultipartEncoderMonitor
from urllib3 import util
from poetry.__version__ import __version__
from poetry.utils.patterns import wheel_file_re
......@@ -327,7 +327,7 @@ class Uploader:
return resp
def _prepare_data(self, data: dict) -> list[tuple[str, str]]:
def _prepare_data(self, data: dict[str, Any]) -> list[tuple[str, str]]:
data_to_send = []
for key, value in data.items():
if not isinstance(value, (list, tuple)):
......
......@@ -4,7 +4,10 @@ from typing import TYPE_CHECKING
if TYPE_CHECKING:
from poetry.core.packages.dependency import Dependency
from poetry.mixology.failure import SolveFailure
from poetry.packages import DependencyPackage
class SolverProblemError(Exception):
......@@ -19,9 +22,11 @@ class SolverProblemError(Exception):
class OverrideNeeded(Exception):
def __init__(self, *overrides: dict) -> None:
def __init__(
self, *overrides: dict[DependencyPackage, dict[str, Dependency]]
) -> None:
self._overrides = overrides
@property
def overrides(self) -> tuple[dict, ...]:
def overrides(self) -> tuple[dict[DependencyPackage, dict[str, Dependency]], ...]:
return self._overrides
......@@ -15,9 +15,14 @@ from typing import TYPE_CHECKING
from typing import Any
from typing import Iterable
from typing import Iterator
from typing import cast
from cleo.ui.progress_indicator import ProgressIndicator
from poetry.core.packages.directory_dependency import DirectoryDependency
from poetry.core.packages.file_dependency import FileDependency
from poetry.core.packages.url_dependency import URLDependency
from poetry.core.packages.utils.utils import get_python_constraint_from_marker
from poetry.core.packages.vcs_dependency import VCSDependency
from poetry.core.semver.empty_constraint import EmptyConstraint
from poetry.core.semver.version import Version
from poetry.core.version.markers import AnyMarker
......@@ -38,11 +43,7 @@ from poetry.vcs.git import Git
if TYPE_CHECKING:
from poetry.core.packages.dependency import Dependency
from poetry.core.packages.directory_dependency import DirectoryDependency
from poetry.core.packages.file_dependency import FileDependency
from poetry.core.packages.package import Package
from poetry.core.packages.url_dependency import URLDependency
from poetry.core.packages.vcs_dependency import VCSDependency
from poetry.core.semver.version_constraint import VersionConstraint
from poetry.core.version.markers import BaseMarker
......@@ -117,7 +118,9 @@ class Provider:
def is_debugging(self) -> bool:
return self._is_debugging
def set_overrides(self, overrides: dict) -> None:
def set_overrides(
self, overrides: dict[DependencyPackage, dict[str, Dependency]]
) -> None:
self._overrides = overrides
def load_deferred(self, load_deferred: bool) -> None:
......@@ -176,12 +179,16 @@ class Provider:
return PackageCollection(dependency, [self._package])
if dependency.is_vcs():
dependency = cast(VCSDependency, dependency)
packages = self.search_for_vcs(dependency)
elif dependency.is_file():
dependency = cast(FileDependency, dependency)
packages = self.search_for_file(dependency)
elif dependency.is_directory():
dependency = cast(DirectoryDependency, dependency)
packages = self.search_for_directory(dependency)
elif dependency.is_url():
dependency = cast(URLDependency, dependency)
packages = self.search_for_url(dependency)
else:
packages = self._pool.find_packages(dependency)
......@@ -249,7 +256,7 @@ class Provider:
def search_for_file(self, dependency: FileDependency) -> list[Package]:
if dependency in self._deferred_cache:
dependency, _package = self._deferred_cache[dependency]
_package = self._deferred_cache[dependency]
package = _package.clone()
else:
......@@ -258,7 +265,7 @@ class Provider:
dependency._constraint = package.version
dependency._pretty_constraint = package.version.text
self._deferred_cache[dependency] = (dependency, package)
self._deferred_cache[dependency] = package
self.validate_package_for_dependency(dependency=dependency, package=package)
......@@ -286,7 +293,7 @@ class Provider:
def search_for_directory(self, dependency: DirectoryDependency) -> list[Package]:
if dependency in self._deferred_cache:
dependency, _package = self._deferred_cache[dependency]
_package = self._deferred_cache[dependency]
package = _package.clone()
else:
......@@ -295,7 +302,7 @@ class Provider:
dependency._constraint = package.version
dependency._pretty_constraint = package.version.text
self._deferred_cache[dependency] = (dependency, package)
self._deferred_cache[dependency] = package
self.validate_package_for_dependency(dependency=dependency, package=package)
......@@ -665,7 +672,7 @@ class Provider:
_deps.append(inverted_marker_dep)
overrides = []
overrides_marker_intersection = AnyMarker()
overrides_marker_intersection: BaseMarker = AnyMarker()
for dep_overrides in self._overrides.values():
for _dep in dep_overrides.values():
overrides_marker_intersection = (
......
......@@ -5,10 +5,10 @@ import time
from collections import defaultdict
from contextlib import contextmanager
from typing import TYPE_CHECKING
from typing import Callable
from typing import FrozenSet
from typing import Iterator
from typing import Tuple
from typing import TypeVar
try:
......@@ -60,7 +60,7 @@ class Solver:
provider = Provider(self._package, self._pool, self._io)
self._provider = provider
self._overrides: list[dict] = []
self._overrides: list[dict[DependencyPackage, dict[str, Dependency]]] = []
@property
def provider(self) -> Provider:
......@@ -71,7 +71,7 @@ class Solver:
with self.provider.use_environment(env):
yield
def solve(self, use_latest: list[str] = None) -> Transaction:
def solve(self, use_latest: list[str] | None = None) -> Transaction:
from poetry.puzzle.transaction import Transaction
with self._provider.progress():
......@@ -97,7 +97,9 @@ class Solver:
)
def solve_in_compatibility_mode(
self, overrides: tuple[dict, ...], use_latest: list[str] = None
self,
overrides: tuple[dict[DependencyPackage, dict[str, Dependency]], ...],
use_latest: list[str] | None = None,
) -> tuple[list[Package], list[int]]:
packages = []
......@@ -125,17 +127,21 @@ class Solver:
return packages, depths
def _solve(self, use_latest: list[str] = None) -> tuple[list[Package], list[int]]:
def _solve(
self, use_latest: list[str] | None = None
) -> tuple[list[Package], list[int]]:
if self._provider._overrides:
self._overrides.append(self._provider._overrides)
locked = defaultdict(list)
locked: dict[str, list[DependencyPackage]] = defaultdict(list)
for package in self._locked.packages:
locked[package.name].append(
DependencyPackage(package.to_dependency(), package)
)
for packages in locked.values():
packages.sort(key=lambda package: package.version, reverse=True)
for dependency_packages in locked.values():
dependency_packages.sort(
key=lambda package: package.package.version, reverse=True
)
try:
result = resolve_version(
......@@ -148,11 +154,8 @@ class Solver:
except SolveFailure as e:
raise SolverProblemError(e)
results = dict(
depth_first_search(
PackageNode(self._package, packages), aggregate_package_nodes
)
)
combined_nodes = depth_first_search(PackageNode(self._package, packages))
results = dict(aggregate_package_nodes(nodes) for nodes in combined_nodes)
# Merging feature packages with base packages
final_packages = []
......@@ -183,6 +186,8 @@ class Solver:
DFSNodeID = Tuple[str, FrozenSet[str], bool]
T = TypeVar("T", bound="DFSNode")
class DFSNode:
def __init__(self, id: DFSNodeID, name: str, base_name: str) -> None:
......@@ -190,7 +195,7 @@ class DFSNode:
self.name = name
self.base_name = base_name
def reachable(self) -> list:
def reachable(self: T) -> list[T]:
return []
def visit(self, parents: list[PackageNode]) -> None:
......@@ -200,9 +205,7 @@ class DFSNode:
return str(self.id)
def depth_first_search(
source: PackageNode, aggregator: Callable
) -> list[tuple[Package, int]]:
def depth_first_search(source: PackageNode) -> list[list[PackageNode]]:
back_edges: dict[DFSNodeID, list[PackageNode]] = defaultdict(list)
visited: set[DFSNodeID] = set()
topo_sorted_nodes: list[PackageNode] = []
......@@ -210,18 +213,18 @@ def depth_first_search(
dfs_visit(source, back_edges, visited, topo_sorted_nodes)
# Combine the nodes by name
combined_nodes = defaultdict(list)
combined_nodes: dict[str, list[PackageNode]] = defaultdict(list)
for node in topo_sorted_nodes:
node.visit(back_edges[node.id])
combined_nodes[node.name].append(node)
combined_topo_sorted_nodes = [
combined_topo_sorted_nodes: list[list[PackageNode]] = [
combined_nodes.pop(node.name)
for node in topo_sorted_nodes
if node.name in combined_nodes
]
return [aggregator(nodes) for nodes in combined_topo_sorted_nodes]
return combined_topo_sorted_nodes
def dfs_visit(
......
......@@ -6,7 +6,7 @@ from typing import TYPE_CHECKING
if TYPE_CHECKING:
from poetry.core.packages.package import Package
from poetry.installation.operations import OperationTypes
from poetry.installation.operations.operation import Operation
class Transaction:
......@@ -28,12 +28,12 @@ class Transaction:
def calculate_operations(
self, with_uninstalls: bool = True, synchronize: bool = False
) -> list[OperationTypes]:
from poetry.installation.operations.install import Install
from poetry.installation.operations.uninstall import Uninstall
from poetry.installation.operations.update import Update
) -> list[Operation]:
from poetry.installation.operations import Install
from poetry.installation.operations import Uninstall
from poetry.installation.operations import Update
operations: list[OperationTypes] = []
operations: list[Operation] = []
for result_package, priority in self._result_packages:
installed = False
......
......@@ -2,3 +2,6 @@ from __future__ import annotations
from poetry.repositories.pool import Pool
from poetry.repositories.repository import Repository
__all__ = ["Pool", "Repository"]
......@@ -9,6 +9,7 @@ from abc import ABC
from collections import defaultdict
from pathlib import Path
from typing import TYPE_CHECKING
from typing import Any
from urllib.parse import quote
import requests
......@@ -232,7 +233,7 @@ class HTTPRepository(CachedRepository, ABC):
f' "{data.version}"'
)
urls = defaultdict(list)
files = []
files: list[dict[str, Any]] = []
for link in links:
if link.is_wheel:
urls["bdist_wheel"].append(link.url)
......@@ -244,7 +245,8 @@ class HTTPRepository(CachedRepository, ABC):
file_hash = f"{link.hash_name}:{link.hash}" if link.hash else None
if not link.hash or (
link.hash_name not in ("sha256", "sha384", "sha512")
link.hash_name is not None
and link.hash_name not in ("sha256", "sha384", "sha512")
and hasattr(hashlib, link.hash_name)
):
with temporary_directory() as temp_dir:
......
......@@ -69,15 +69,15 @@ class LinkSource:
if m:
name = canonicalize_name(m.group("name"))
version = m.group("ver")
version_string = m.group("ver")
else:
info, ext = link.splitext()
match = self.VERSION_REGEX.match(info)
if match:
version = match.group(2)
version_string = match.group(2)
with contextlib.suppress(ValueError):
version = Version.parse(version)
version = Version.parse(version_string)
return Package(name, version, source_url=link.url)
......
from __future__ import annotations
from contextlib import suppress
from typing import TYPE_CHECKING
from poetry.repositories.exceptions import PackageNotFound
......@@ -136,18 +135,14 @@ class Pool(Repository):
raise ValueError(f'Repository "{repository}" does not exist.')
if repository is not None and not self._ignore_repository_names:
with suppress(PackageNotFound):
return self.repository(repository).package(name, version, extras=extras)
else:
for repo in self._repositories:
try:
package = repo.package(name, version, extras=extras)
except PackageNotFound:
continue
if package:
self._packages.append(package)
return package
raise PackageNotFound(f"Package {name} ({version}) not found.")
......
......@@ -8,16 +8,19 @@ from poetry.core.semver.helpers import parse_constraint
from poetry.core.semver.version_constraint import VersionConstraint
from poetry.core.semver.version_range import VersionRange
from poetry.repositories.exceptions import PackageNotFound
if TYPE_CHECKING:
from poetry.core.packages.dependency import Dependency
from poetry.core.packages.package import Package
from poetry.core.packages.utils.link import Link
from poetry.core.semver.helpers import VersionTypes
class Repository:
def __init__(self, name: str = None, packages: list[Package] = None) -> None:
def __init__(
self, name: str | None = None, packages: list[Package] | None = None
) -> None:
self._name = name
self._packages: list[Package] = []
......@@ -94,7 +97,7 @@ class Repository:
@staticmethod
def _get_constraints_from_dependency(
dependency: Dependency,
) -> tuple[VersionTypes, bool]:
) -> tuple[VersionConstraint, bool]:
constraint = dependency.constraint
if constraint is None:
constraint = "*"
......@@ -131,3 +134,5 @@ class Repository:
for package in self.packages:
if name == package.name and package.version.text == version:
return package.clone()
raise PackageNotFound(f"Package {name} ({version}) not found.")
......@@ -14,7 +14,7 @@ else:
WINDOWS = sys.platform == "win32"
def decode(string: str, encodings: list[str] | None = None) -> str:
def decode(string: bytes | str, encodings: list[str] | None = None) -> str:
if not isinstance(string, bytes):
return string
......
......@@ -114,6 +114,7 @@ def get_package_version_display_string(
package: Package, root: Path | None = None
) -> str:
if package.source_type in ["file", "directory"] and root:
assert package.source_url is not None
path = Path(os.path.relpath(package.source_url, root.as_posix())).as_posix()
return f"{package.version} {path}"
......
......@@ -367,7 +367,11 @@ class Git:
if not is_revision_sha(revision=current_sha):
# head is not a sha, this will cause issues later, lets reset
remove_directory(target, force=True)
elif refspec.is_sha and current_sha.startswith(refspec.revision):
elif (
refspec.is_sha
and refspec.revision is not None
and current_sha.startswith(refspec.revision)
):
# if revision is used short-circuit remote fetch head matches
return current_repo
......
......@@ -21,7 +21,7 @@ class VersionSelector:
target_package_version: str | None = None,
allow_prereleases: bool = False,
source: str | None = None,
) -> Package | bool:
) -> Package | None:
"""
Given a package name and optional version,
returns the latest Package that matches
......@@ -40,7 +40,7 @@ class VersionSelector:
only_prereleases = all(c.version.is_unstable() for c in candidates)
if not candidates:
return False
return None
package = None
for candidate in candidates:
......@@ -55,8 +55,6 @@ class VersionSelector:
if package is None or package.version < candidate.version:
package = candidate
if package is None:
return False
return package
def find_recommended_require_version(self, package: Package) -> str:
......
......@@ -26,11 +26,10 @@ from poetry.repositories.exceptions import PackageNotFound
if TYPE_CHECKING:
from poetry.core.packages.dependency import Dependency
from poetry.core.packages.types import DependencyTypes
from poetry.core.semver.version import Version
from tomlkit.toml_document import TOMLDocument
from poetry.installation.operations import OperationTypes
from poetry.installation.operations.operation import Operation
from poetry.poetry import Poetry
FIXTURE_PATH = Path(__file__).parent / "fixtures"
......@@ -46,7 +45,7 @@ def get_dependency(
groups: list[str] | None = None,
optional: bool = False,
allows_prereleases: bool = False,
) -> DependencyTypes:
) -> Dependency:
if constraint is None:
constraint = "*"
......@@ -134,19 +133,19 @@ class TestExecutor(Executor):
def removals(self) -> list[Package]:
return self._uninstalls
def _do_execute_operation(self, operation: OperationTypes) -> None:
def _do_execute_operation(self, operation: Operation) -> None:
super()._do_execute_operation(operation)
if not operation.skipped:
getattr(self, f"_{operation.job_type}s").append(operation.package)
def _execute_install(self, operation: OperationTypes) -> int:
def _execute_install(self, operation: Operation) -> int:
return 0
def _execute_update(self, operation: OperationTypes) -> int:
def _execute_update(self, operation: Operation) -> int:
return 0
def _execute_remove(self, operation: OperationTypes) -> int:
def _execute_remove(self, operation: Operation) -> int:
return 0
......
......@@ -47,7 +47,7 @@ from tests.repositories.test_pypi_repository import MockRepository
if TYPE_CHECKING:
from pytest_mock import MockerFixture
from poetry.installation.operations import OperationTypes
from poetry.installation.operations.operation import Operation
from poetry.packages import DependencyPackage
from poetry.utils.env import Env
from tests.conftest import Config
......@@ -81,19 +81,19 @@ class Executor(BaseExecutor):
def removals(self) -> list[DependencyPackage]:
return self._uninstalls
def _do_execute_operation(self, operation: OperationTypes) -> None:
def _do_execute_operation(self, operation: Operation) -> None:
super()._do_execute_operation(operation)
if not operation.skipped:
getattr(self, f"_{operation.job_type}s").append(operation.package)
def _execute_install(self, operation: OperationTypes) -> int:
def _execute_install(self, operation: Operation) -> int:
return 0
def _execute_update(self, operation: OperationTypes) -> int:
def _execute_update(self, operation: Operation) -> int:
return 0
def _execute_uninstall(self, operation: OperationTypes) -> int:
def _execute_uninstall(self, operation: Operation) -> int:
return 0
......
......@@ -32,7 +32,7 @@ from tests.repositories.test_pypi_repository import MockRepository as MockPyPIRe
if TYPE_CHECKING:
import httpretty
from poetry.installation.operations import OperationTypes
from poetry.installation.operation.operation import Operation
from poetry.puzzle.transaction import Transaction
DEFAULT_SOURCE_REF = (
......@@ -94,7 +94,7 @@ def check_solver_result(
transaction: Transaction,
expected: list[dict[str, Any]],
synchronize: bool = False,
) -> list[OperationTypes]:
) -> list[Operation]:
for e in expected:
if "skipped" not in e:
e["skipped"] = False
......
......@@ -9,10 +9,10 @@ from poetry.puzzle.transaction import Transaction
if TYPE_CHECKING:
from poetry.installation.operations import OperationTypes
from poetry.installation.operations.operation import Operation
def check_operations(ops: list[OperationTypes], expected: list[dict[str, Any]]) -> None:
def check_operations(ops: list[Operation], expected: list[dict[str, Any]]) -> None:
for e in expected:
if "skipped" not in e:
e["skipped"] = False
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment