diff --git a/README.md b/README.md index 5399d99..652b5b0 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ The tables below show the status for features and operating systems. **cpp-dev** is designed as a tool around the following external tools: - Package Management: Conan2 -- Build System: Ninja +- Build System: CMake, Ninja - Toolchain: LLVM-based (clang, clang-format, clang-coverage, clang-sanitizer, clang-tidy) - Test framework: gtest and gmock - Code coverage: lcov diff --git a/docs/src/ch01_introduction.md b/docs/src/ch01_introduction.md index 9bff098..e02b428 100644 --- a/docs/src/ch01_introduction.md +++ b/docs/src/ch01_introduction.md @@ -45,7 +45,7 @@ Therefore, all **cpp-dev** packages may also be used outside of **cpp-dev** as w A workflow using **cpp-dev** could look like: -* **Initialize project**: `cpd init [--std c++17] [--version ]` +* **Initialize project**: `cpd init [--std c++20] [--version ]` * **Add external dependency**: `cpd add-dep @` * **Update external dependencies**: `cpd update-dep [@]` * **Build**: `cpd build [release | debug]` diff --git a/mypy.ini b/mypy.ini index 4bb0597..f425af5 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,3 +1,3 @@ [mypy] exclude = conan -ignore_missing_imports = True +ignore_missing_imports = True \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index ed12873..7352246 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,3 +28,6 @@ cpd = "cpp_dev.ui.cli:main" [build-system] requires = ["hatchling"] build-backend = "hatchling.build" + +[tool.pytest.ini_options] +markers = ["conan_remote"] diff --git a/ruff.toml b/ruff.toml index a98c3b7..a439311 100644 --- a/ruff.toml +++ b/ruff.toml @@ -1,7 +1,7 @@ exclude = ["conan"] indent-width = 4 line-length = 120 -target-version = "py313" +target-version = "py312" [lint] ignore = [ @@ -15,6 +15,7 @@ ignore = [ "EM101", "EM102", "G004", + "ISC001", "PLR2004", "TC001", "TRY003", diff --git a/src/cpp_dev/common/process.py b/src/cpp_dev/common/process.py index f82278e..75f714d 100644 --- a/src/cpp_dev/common/process.py +++ b/src/cpp_dev/common/process.py @@ -17,7 +17,7 @@ def run_command(command: str, *args: str) -> tuple[int, str, str]: This function blocks until the command has finished. """ logging.debug(f"Running command: {command} {args}") - result = subprocess.run([command, *args], check=True, capture_output=True) # noqa: S603 + result = subprocess.run([command, *args], check=False, capture_output=True) # noqa: S603 logging.debug(f"Command return code: {result.returncode}") diff --git a/src/cpp_dev/common/types.py b/src/cpp_dev/common/types.py index 9717adc..338051f 100644 --- a/src/cpp_dev/common/types.py +++ b/src/cpp_dev/common/types.py @@ -9,4 +9,4 @@ # Public API ### ############################################################################### -CppStandard = Literal["c++11", "c++14", "c++17", "c++20", "c++23"] +CppStandard = Literal["c++20"] diff --git a/src/cpp_dev/conan/package.py b/src/cpp_dev/conan/package.py deleted file mode 100644 index 02aaac4..0000000 --- a/src/cpp_dev/conan/package.py +++ /dev/null @@ -1,46 +0,0 @@ -# Copyright (c) 2024 Andi Hellmund. All rights reserved. - -# This work is licensed under the terms of the BSD-3-Clause license. -# For a copy, see . - -from pathlib import Path - -from cpp_dev.common.types import SemanticVersion -from cpp_dev.common.utils import create_tmp_dir -from cpp_dev.dependency.types import PackageDependency -from cpp_dev.tool.init import get_conan_home_dir - -from ..dependency.conan.command_wrapper import conan_list -from ..dependency.conan.setup import CONAN_REMOTE -from ..dependency.conan.types import ConanPackageReference -from ..dependency.conan.utils import conan_env, create_conanfile - -############################################################################### -# Public API ### -############################################################################### - -def get_available_versions(repository: str, name: str) -> list[SemanticVersion]: - """Retrieve available versions for a package represented by repository (aka. Conan user) and name. - - Result: - The versions get sorted in reverse order such that the latest version is first in the list. - """ - with conan_env(get_conan_home_dir()): - - - -def compute_dependency_graph(package_refs: list[PackageDependency]) -> None: - """Retrieve the dependency graph for the given package dependencies.""" - with conan_env(get_conan_home_dir()): - with create_tmp_dir() as tmp_dir: - conanfile_path = create_conanfile(tmp_dir, package_refs) - - -############################################################################### -# Implementation ### -############################################################################### - - - - - diff --git a/src/cpp_dev/dependency/conan/command_wrapper.py b/src/cpp_dev/dependency/conan/command_wrapper.py index 4fe94be..0925589 100644 --- a/src/cpp_dev/dependency/conan/command_wrapper.py +++ b/src/cpp_dev/dependency/conan/command_wrapper.py @@ -3,28 +3,44 @@ # This work is licensed under the terms of the BSD-3-Clause license. # For a copy, see . -import json +import re from collections.abc import Mapping from pathlib import Path -from typing import Literal, Optional +from typing import Literal from pydantic import BaseModel, RootModel from cpp_dev.common.process import run_command, run_command_assert_success -from cpp_dev.dependency.provider import Dependency -from .types import ConanPackageReference +from .types import ConanPackageReferenceWithSemanticVersion ############################################################################### # Public API ### ############################################################################### +class ConanCommandException(Exception): + """Exception for raising issues during Conan command execution.""" + def __init__(self, command: str, msg: str) -> None: + self._command = command + self._msg = msg + super().__init__(f"{self._command} failed: {self._msg}") + +ConanSettingName = Literal["compiler", "compiler.cppstd"] +ConanSettings = dict[ConanSettingName, object] + + +############################ +### Conan Config Install ### +############################ def conan_config_install(conan_config_dir: Path) -> None: """Run "conan config install".""" run_command("conan", "config", "install", str(conan_config_dir)) +########################## +### Conan Remote Login ### +########################## def conan_remote_login(remote: str, user: str, password: str) -> None: """Run "conan remote login".""" run_command_assert_success( @@ -37,10 +53,11 @@ def conan_remote_login(remote: str, user: str, password: str) -> None: password, ) -class ConanRemoteListResult(RootModel): - root: Mapping[str, Mapping[str, dict]] +### Conan List +class ConanListResult(RootModel): + root: Mapping[str, Mapping[ConanPackageReferenceWithSemanticVersion, dict]] -def conan_list(remote: str, name: str) -> Mapping[ConanPackageReference, dict]: +def conan_list(remote: str, name: str) -> Mapping[ConanPackageReferenceWithSemanticVersion, dict]: stdout, _ = run_command_assert_success( "conan", "list", @@ -48,10 +65,15 @@ def conan_list(remote: str, name: str) -> Mapping[ConanPackageReference, dict]: f"--remote={remote}", f"{name}/", ) - return json.loads(stdout)[remote] + parsed_data = ConanListResult.model_validate_json(stdout) + return parsed_data.root[remote] + +############################### +### Conan Graph Build-Order ### +############################### class ConanPackageInfo(BaseModel): - settings: Mapping[str, str] | None = None + settings: ConanSettings | None = None class ConanPackageAttributes(BaseModel): info: ConanPackageInfo @@ -64,9 +86,39 @@ class ConanRecipeAttributes(BaseModel): class ConanGraphBuildOrder(BaseModel): order: list[list[ConanRecipeAttributes]] -def conan_graph_buildorder(conanfile_path: Path, profile: str) -> ConanGraphBuildOrder: + +COMMAND_GRAPH_BUILDORDER = "graph-buildorder" + +def _handle_package_resolution_error(stderr: str) -> None: + regex_unable_to_find = re.compile(r"Unable to find '([^']+)'") + match = regex_unable_to_find.search(stderr) + if match: + raise ConanCommandException( + command=COMMAND_GRAPH_BUILDORDER, + msg=f"unable to find package '{match.group(1)}'", + ) + +def _handle_package_version_conflict(stderr: str) -> None: + regex_version_conflict = re.compile(r"Version conflict: Conflict between ([^ ]+) and ([^ ]+) in the graph") + match = regex_version_conflict.search(stderr) + if match: + raise ConanCommandException( + command=COMMAND_GRAPH_BUILDORDER, + msg=f"version conflict between '{match.group(1)}' and '{match.group(2)}'", + ) + +def _handle_graph_buildorder_error(stderr: str) -> None: + _handle_package_resolution_error(stderr) + _handle_package_version_conflict(stderr) + + raise ConanCommandException( + command=COMMAND_GRAPH_BUILDORDER, + msg="generic error", + ) + +def conan_graph_buildorder(conanfile_path: Path, profile: str, settings: ConanSettings) -> ConanGraphBuildOrder: """Run "conan graph buildorder".""" - stdout, _ = run_command_assert_success( + command = [ "conan", "graph", "build-order", @@ -74,20 +126,40 @@ def conan_graph_buildorder(conanfile_path: Path, profile: str) -> ConanGraphBuil "-pr:a", profile, "-f", "json", "--order-by", "recipe", + ] + for key, value in settings.items(): + command.extend(["-s:a", f"{key}={value}"]) + rc, stdout, stderr = run_command( + *command ) + if rc != 0: + _handle_graph_buildorder_error(stderr) + return ConanGraphBuildOrder.model_validate_json(stdout) -def conan_create(package_dir: Path, profile: str) -> None: +#################### +### Conan Create ### +#################### + +def conan_create(package_dir: Path, profile: str, settings: ConanSettings) -> None: """Run "conan create".""" - run_command_assert_success( + command = [ "conan", "create", str(package_dir), "-pr:a", profile, + ] + for key, value in settings.items(): + command.extend(["-s:a", f"{key}={value}"]) + run_command_assert_success( + *command ) -def conan_upload(ref: ConanPackageReference, remote: str) -> None: +#################### +### Conan Upload ### +#################### +def conan_upload(ref: ConanPackageReferenceWithSemanticVersion, remote: str) -> None: """Run "conan upload".""" run_command_assert_success( "conan", diff --git a/src/cpp_dev/dependency/conan/config/profiles/ubuntu-24.04 b/src/cpp_dev/dependency/conan/config/profiles/ubuntu-24.04-x86_64 similarity index 100% rename from src/cpp_dev/dependency/conan/config/profiles/ubuntu-24.04 rename to src/cpp_dev/dependency/conan/config/profiles/ubuntu-24.04-x86_64 diff --git a/src/cpp_dev/dependency/conan/config/settings.yml b/src/cpp_dev/dependency/conan/config/settings.yml index 66b39ad..4fcc89b 100644 --- a/src/cpp_dev/dependency/conan/config/settings.yml +++ b/src/cpp_dev/dependency/conan/config/settings.yml @@ -5,10 +5,10 @@ arch: [x86_64] compiler: gcc: version: ["13"] - cppstd: [None, 11, 14, 17, 20, 23] + cppstd: [None, 20] libcxx: [libstdc++, libstdc++11] clang: version: ["19"] libcxx: [None, libstdc++, libstdc++11, libc++, c++_shared, c++_static] - cppstd: [None, 11, 14, 17, 20, 23] + cppstd: [None, 20] build_type: [None, Debug, Release, RelWithDebInfo, MinSizeRel] \ No newline at end of file diff --git a/src/cpp_dev/dependency/conan/provider.py b/src/cpp_dev/dependency/conan/provider.py index 4d136a4..53f8852 100644 --- a/src/cpp_dev/dependency/conan/provider.py +++ b/src/cpp_dev/dependency/conan/provider.py @@ -7,16 +7,24 @@ from abc import ABC, abstractmethod from dataclasses import dataclass +from itertools import chain from pathlib import Path from cpp_dev.common.types import CppStandard +from cpp_dev.common.utils import create_tmp_dir from cpp_dev.common.version import SemanticVersion -from cpp_dev.dependency.conan.command_wrapper import conan_list +from cpp_dev.dependency.conan.command_wrapper import (ConanRecipeAttributes, + ConanSettings, + conan_graph_buildorder, + conan_list) from cpp_dev.dependency.conan.setup import CONAN_REMOTE -from cpp_dev.dependency.conan.types import ConanPackageReference -from cpp_dev.dependency.conan.utils import conan_env -from cpp_dev.dependency.provider import Dependency, DependencyProvider +from cpp_dev.dependency.conan.types import \ + ConanPackageReferenceWithSemanticVersion +from cpp_dev.dependency.conan.utils import conan_env, create_conanfile +from cpp_dev.dependency.provider import (DependencyIdentifier, + DependencyProvider) from cpp_dev.dependency.specifier import DependencySpecifier +from cpp_dev.dependency.types import DependencySpecifierParts ############################################################################### # Public API ### @@ -24,8 +32,10 @@ class ConanDependencyProvider(DependencyProvider): - def __init__(self, conan_home_dir: Path) -> None: + def __init__(self, conan_home_dir: Path, profile: str, settings: dict[ConanSetting, object] | None = None) -> None: self._conan_home_dir = conan_home_dir + self._profile = profile + self._settings = settings def fetch_versions(self, repository: str, name: str) -> list[SemanticVersion]: with conan_env(self._conan_home_dir): @@ -33,8 +43,14 @@ def fetch_versions(self, repository: str, name: str) -> list[SemanticVersion]: available_versions = sorted([ref.version for ref in package_references], reverse=True) return available_versions - def collect_dependency_hull(self, deps: list[DependencySpecifier]) -> list[Dependency]: - ... # Implementation using Conan package manager + def collect_dependency_hull(self, deps: list[DependencySpecifier]) -> list[DependencyIdentifier]: + with conan_env(self._conan_home_dir): + with create_tmp_dir() as tmp_dir: + conanfile_path = create_conanfile(tmp_dir, deps) + conan_settings = self._settings if self._settings else {} + build_order = conan_graph_buildorder(conanfile_path, self._profile, conan_settings) + return _construct_depenencies(build_order.order) + def install_dependencies(self, deps: list[DependencySpecifier]) -> list[DependencySpecifier]: ... # Implementation using Conan package manager @@ -44,11 +60,22 @@ def install_dependencies(self, deps: list[DependencySpecifier]) -> list[Dependen # Implementation ### ############################################################################### -def _retrieve_conan_package_references(repository: str, name: str) -> list[ConanPackageReference]: +def _retrieve_conan_package_references(repository: str, name: str) -> list[ConanPackageReferenceWithSemanticVersion]: package_data = conan_list(CONAN_REMOTE, name) package_references = [ ref for ref in package_data.keys() if ref.user == repository ] - return package_references \ No newline at end of file + return package_references + +def _construct_depenencies(build_order: list[list[ConanRecipeAttributes]]) -> set[DependencyIdentifier]: + all_packages = [element for sublist in build_order for element in sublist] + dependencies = set() + for attributes in all_packages: + ref = ConanPackageReferenceWithSemanticVersion.from_raw_string_with_revision(attributes.ref) + dependencies.add( + DependencyIdentifier(repository=ref.user, name=ref.name, version=ref.version) + ) + return dependencies + \ No newline at end of file diff --git a/src/cpp_dev/dependency/conan/types.py b/src/cpp_dev/dependency/conan/types.py index cfcbf38..b958235 100644 --- a/src/cpp_dev/dependency/conan/types.py +++ b/src/cpp_dev/dependency/conan/types.py @@ -15,14 +15,27 @@ # Public API ### ############################################################################### + +class ConanPackageReferenceWithVersionRanges(str): + """A generic Conan package reference supporting version ranges. + + This package reference has the format: name/[version_ranges]@user/channel. + """ + -class ConanPackageReference(RootModel): +class ConanPackageReferenceWithSemanticVersion(RootModel): """A Conan package reference in the format name/version@user/channel.""" root: str + @staticmethod + def from_raw_string_with_revision(raw_string: str) -> ConanPackageReferenceWithSemanticVersion: + REVISION_MARKER = "#" + raw_string, revision = raw_string.rsplit(REVISION_MARKER, 1) + return ConanPackageReferenceWithSemanticVersion(raw_string) + @model_validator(mode="after") - def validate_reference(self) -> ConanPackageReference: + def validate_reference(self) -> ConanPackageReferenceWithSemanticVersion: CONAN_REFERENCE_PATTERN = r"(?P[a-zA-Z0-9_]+)/(?P\d+\.\d+\.\d+)@(?P[a-zA-Z0-9_]+)/(?P[a-zA-Z0-9_]+)" match = re.match(CONAN_REFERENCE_PATTERN, self.root) if not match: @@ -33,6 +46,8 @@ def validate_reference(self) -> ConanPackageReference: self._user = match.group("user") self._channel = match.group("channel") + return self + @property def name(self) -> str: return self._name @@ -53,4 +68,5 @@ def __hash__(self) -> int: return hash(self.root) def __str__(self) -> str: - return f"{self._name}/{self._version}@{self._user}/{self._channel}" \ No newline at end of file + return f"{self._name}/{self._version}@{self._user}/{self._channel}" + \ No newline at end of file diff --git a/src/cpp_dev/dependency/conan/utils.py b/src/cpp_dev/dependency/conan/utils.py index 609d88f..0cb38fc 100644 --- a/src/cpp_dev/dependency/conan/utils.py +++ b/src/cpp_dev/dependency/conan/utils.py @@ -9,6 +9,9 @@ from cpp_dev.common.utils import updated_env from cpp_dev.common.version import SemanticVersion +from cpp_dev.dependency.conan.types import ( + ConanPackageReferenceWithSemanticVersion, + ConanPackageReferenceWithVersionRanges) from cpp_dev.dependency.specifier import DependencySpecifier ############################################################################### @@ -35,9 +38,14 @@ def create_conanfile(tmp_dir: Path, package_refs: list[DependencySpecifier]) -> conanfile_path.write_text(content) return conanfile_path -def compose_conan_package_reference(ref: DependencySpecifier) -> str: +def compose_conan_package_reference(ref: DependencySpecifier) -> ConanPackageReferenceWithVersionRanges: """Compose a Conan package reference from a package dependency.""" - return f"{ref.name}/{_get_conan_package_version(ref)}@{ref.repository}/{DEFAULT_CONAN_CHANNEL}" + return ConanPackageReferenceWithVersionRanges(f"{ref.name}/{_get_conan_package_version(ref)}@{ref.repository}/{DEFAULT_CONAN_CHANNEL}") + + +############################################################################### +# Implementation ### +############################################################################### def _get_conan_package_version(ref: DependencySpecifier) -> str: if isinstance(ref.version_spec, SemanticVersion): @@ -46,4 +54,5 @@ def _get_conan_package_version(ref: DependencySpecifier) -> str: bound_str = " ".join([f"{bound.operand.value}{bound.version}" for bound in ref.version_spec]) return f"[{bound_str}]" else: - raise ValueError("Unsupported version specification: latest") \ No newline at end of file + raise ValueError("Unsupported version specification: latest") + diff --git a/src/cpp_dev/dependency/provider.py b/src/cpp_dev/dependency/provider.py index 837e6fc..91259dc 100644 --- a/src/cpp_dev/dependency/provider.py +++ b/src/cpp_dev/dependency/provider.py @@ -8,7 +8,6 @@ from abc import ABC, abstractmethod from dataclasses import dataclass -from cpp_dev.common.types import CppStandard from cpp_dev.common.version import SemanticVersion from .specifier import DependencySpecifier @@ -43,19 +42,13 @@ def from_str(id_str: str) -> DependencyIdentifier: raise ValueError(f"Invalid dependency id string: {id_str}") return DependencyIdentifier(parts[0], parts[1], SemanticVersion(parts[2])) + def __hash__(self) -> int: + return hash((self.repository, self.name, self.version)) + def __str__(self) -> str: return f"{self.repository}/{self.name}/{self.version}" -@dataclass -class Dependency: - """A software package containing libraries, headers and executables.""" - - id: DependencyIdentifier - cpp_standard: CppStandard | None - deps: list[DependencyIdentifier] - - class DependencyProvider(ABC): """Abstract base class for dependency providers. @@ -78,7 +71,7 @@ def fetch_versions(self, repository: str, name: str) -> list[SemanticVersion]: """ @abstractmethod - def collect_dependency_hull(self, deps: list[DependencySpecifier]) -> list[Dependency]: + def collect_dependency_hull(self, deps: list[DependencySpecifier]) -> set[DependencyIdentifier]: """Collect the dependency hull for a list of dependencies. Args: diff --git a/src/cpp_dev/project/config.py b/src/cpp_dev/project/config.py index 876bd32..1d78651 100644 --- a/src/cpp_dev/project/config.py +++ b/src/cpp_dev/project/config.py @@ -89,6 +89,21 @@ def update_dependencies( return updated_config +def validate_dependencies(project_config: ProjectConfig) -> None: + """Check that a dependency identified by its name (without repository) exists only once. + + Dependencies are in general identified by a name and their repository, e.g. official/cpd. + It is, however, possible to add a dependency with the same name from different repositories, e.g. + official/cpd and user/cpd. In this case, it is assumed that both dependencies are the same which + is not supported in C++ because it might lead to undefined behavior. + """ + seen_names = set() + for dep in project_config.dependencies + project_config.dev_dependencies + project_config.cpd_dependencies: + if dep.name in seen_names: + raise ValueError(f"Dependency '{dep.name}' is defined multiple times.") + seen_names.add(dep.name) + + ############################################################################### # Implementation ### ############################################################################### diff --git a/src/cpp_dev/project/core.py b/src/cpp_dev/project/core.py index 5b07022..4e72989 100644 --- a/src/cpp_dev/project/core.py +++ b/src/cpp_dev/project/core.py @@ -6,9 +6,20 @@ from pathlib import Path from textwrap import dedent -from cpp_dev.dependency.provider import DependencyProvider - -from .config import ProjectConfig, create_project_config +from cpp_dev.common.version import SemanticVersionWithOptionalParts +from cpp_dev.dependency.provider import DependencyIdentifier, DependencyProvider +from cpp_dev.dependency.specifier import DependencySpecifier +from cpp_dev.dependency.types import DependencySpecifierParts, VersionSpecBound, VersionSpecBoundOperand + +from .config import ( + DependencyType, + ProjectConfig, + create_project_config, + load_project_config, + store_project_config, + update_dependencies, + validate_dependencies, +) from .lockfile import create_initial_lock_file from .path_composition import compose_include_file, compose_source_file @@ -29,6 +40,16 @@ def project_dir(self) -> Path: """Return the path to the project directory.""" return self._project_dir + def add_package_dependency(self, deps: list[DependencySpecifier], dep_type: DependencyType) -> None: + """Add package dependencies to the project for the given type.""" + refined_deps = _refine_package_dependencies(self._dependency_provider, deps) + project_config = load_project_config(self.project_dir) + update_dependencies(project_config, refined_deps, dep_type) + validate_dependencies(project_config) + dependency_hull = _obtain_dependency_hull(project_config, self._dependency_provider) + _write_lock_file(self.project_dir, dependency_hull) + store_project_config(self.project_dir, project_config) + def setup_project( project_config: ProjectConfig, @@ -121,6 +142,59 @@ def _create_library_test_file(project_dir: Path, name: str) -> None: ) -def _add_default_cpd_dependencies(project_dir: Path) -> None: - # add_package_dependency(project_dir, [PackageDependency("llvm"), PackageDependency("gtest")], "cpd") # noqa: ERA001 - ... +def _add_default_cpd_dependencies(project: Project) -> None: + project.add_package_dependency( + [ + DependencySpecifier("llvm"), + DependencySpecifier("gtest"), + ], + "cpd", + ) + + +DEFAULT_REPOSITORY = "official" + + +def _refine_package_dependencies( + dep_provider: DependencyProvider, deps: list[DependencySpecifier] +) -> list[DependencySpecifier]: + """Refine the package dependencies in case of defaults were chosen. + + The refinement includes (in order): + o Default repository "official" + o Latest resolved version in case of "latest" + + This step is performed to assure that package dependencies with "latest" do not get an older version + than the latest one at the time of resolution. This is important in case a versions gets removed. + """ + updated_deps = [] + for dep in deps: + repository = dep.repository if dep.repository is not None else DEFAULT_REPOSITORY + version_spec = dep.version_spec + if dep.version_spec == "latest": + available_versions = dep_provider.fetch_versions(repository, dep.name) + if len(available_versions) == 0: + raise ValueError(f"No available versions for package {dep.name} at repository {dep.repository}.") + version_spec = [ + VersionSpecBound( + operand=VersionSpecBoundOperand.GREATER_THAN_OR_EQUAL, + version=SemanticVersionWithOptionalParts.from_semantic_version(available_versions[0]), + ) + ] + updated_deps.append( + DependencySpecifier.from_parts(DependencySpecifierParts(repository, dep.name, version_spec)) + ) + return updated_deps + + +def _obtain_dependency_hull( + project_config: ProjectConfig, dep_provider: DependencyProvider +) -> set[DependencyIdentifier]: + """Obtain the dependency hull for the given project configuration.""" + return dep_provider.collect_dependency_hull( + project_config.dependencies + project_config.dev_dependencies + project_config.cpd_dependencies + ) + + +def _write_lock_file(_project_dir: Path, _dependency_hull: set[DependencyIdentifier]) -> None: + """Write the lock file for the given dependency hull.""" diff --git a/src/cpp_dev/project/dependency/utils.py b/src/cpp_dev/project/dependency/utils.py deleted file mode 100644 index 4715d59..0000000 --- a/src/cpp_dev/project/dependency/utils.py +++ /dev/null @@ -1,47 +0,0 @@ -# Copyright (c) 2024 Andi Hellmund. All rights reserved. - -# This work is licensed under the terms of the BSD-3-Clause license. -# For a copy, see . - -from copy import deepcopy - -from cpp_dev.conan.package import get_available_versions - -from ...dependency.types import PackageDependency -from .parts import SemanticVersionWithOptionalParts, VersionSpecBound, VersionSpecBoundOperand - -############################################################################### -# Public API ### -############################################################################### - - -DEFAULT_REPOSITORY = "official" - - -def refine_package_dependencies(deps: list[PackageDependency]) -> list[PackageDependency]: - """Refine the package dependencies in case of defaults were chosen. - - The refinement includes (in order): - o Default repository "official" - o Latest resolved version in case of "latest" - - This step is performed to assure that package dependencies with "latest" do not get an older version - than the latest one at the time of resolution. This is important in case a versions gets removed. - """ - updated_deps = [] - for dep in deps: - parts = deepcopy(dep.parts) - if parts.repository is None: - parts.repository = DEFAULT_REPOSITORY - if parts.version_spec == "latest": - available_versions = get_available_versions(parts.repository, parts.name) - if len(available_versions) == 0: - raise ValueError(f"No available versions for package {parts.name} at repository {parts.repository}.") - parts.version_spec = [ - VersionSpecBound( - operand=VersionSpecBoundOperand.GREATER_THAN_OR_EQUAL, - version=SemanticVersionWithOptionalParts.from_semantic_version(available_versions[0]), - ) - ] - updated_deps.append(PackageDependency.from_parts(parts)) - return updated_deps diff --git a/src/cpp_dev/project/management.py b/src/cpp_dev/project/management.py deleted file mode 100644 index 5bf03a5..0000000 --- a/src/cpp_dev/project/management.py +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright (c) 2024 Andi Hellmund. All rights reserved. - -# This work is licensed under the terms of the BSD-3-Clause license. -# For a copy, see . - -from pathlib import Path -from textwrap import dedent -from typing import get_args - -from cpp_dev.conan.package import compute_dependency_graph - -from ..dependency.types import PackageDependency -from .config import create_project_config, load_project_config, update_dependencies -from .dependency.utils import refine_package_dependencies -from .lockfile import create_initial_lock_file -from .path_composition import compose_include_file, compose_source_file -from .types import DependencyType, ProjectConfig - -############################################################################### -# Public API ### -############################################################################### - - -def add_package_dependency(project_dir: Path, deps: list[PackageDependency], dep_type: DependencyType) -> None: - """Add package dependencies to the project for the given type.""" - refined_deps = refine_package_dependencies(deps) - project_config = load_project_config(project_dir) - updated_config = update_dependencies(project_config, refined_deps, dep_type) - _collect_dependency_graph(updated_config) - - -############################################################################### -# Implementation ### -############################################################################### - - -def _collect_dependency_graph(project_config: ProjectConfig) -> None: - all_package_deps = [ - dep for dep_type in get_args(DependencyType) for dep in project_config.get_dependencies(dep_type) - ] - compute_dependency_graph(all_package_deps) diff --git a/src/cpp_dev/tool/init.py b/src/cpp_dev/tool/init.py index dc9ebfe..b2018b9 100644 --- a/src/cpp_dev/tool/init.py +++ b/src/cpp_dev/tool/init.py @@ -9,7 +9,7 @@ from filelock import FileLock, Timeout from cpp_dev.common.utils import ensure_dir_exists -from cpp_dev.dependency.conan.setup import initialize_conan +from cpp_dev.dependency.conan.setup import get_conan_config_source_dir, initialize_conan from cpp_dev.tool.version import get_cpd_version_from_code, read_version_file, write_version_file ############################################################################### @@ -91,7 +91,7 @@ def _initialize_cpd(cpd_dir: Path) -> None: def _initialize_conan(cpd_dir: Path) -> None: conan_dir = _compose_conan_home(cpd_dir) ensure_dir_exists(conan_dir) - initialize_conan(conan_dir) + initialize_conan(conan_dir, get_conan_config_source_dir()) write_version_file(cpd_dir, get_cpd_version_from_code()) diff --git a/src/cpp_dev/conan/__init__.py b/src/tests/__init__.py similarity index 100% rename from src/cpp_dev/conan/__init__.py rename to src/tests/__init__.py diff --git a/src/cpp_dev/project/dependency/__init__.py b/src/tests/cpp_dev/__init__.py similarity index 100% rename from src/cpp_dev/project/dependency/__init__.py rename to src/tests/cpp_dev/__init__.py diff --git a/src/tests/cpp_dev/conan/__init__.py b/src/tests/cpp_dev/conan/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/tests/cpp_dev/conan/_test_package.py b/src/tests/cpp_dev/conan/_test_package.py deleted file mode 100644 index d325255..0000000 --- a/src/tests/cpp_dev/conan/_test_package.py +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright (c) 2024 Andi Hellmund. All rights reserved. - -# This work is licensed under the terms of the BSD-3-Clause license. -# For a copy, see . - -from unittest.mock import patch - -from cpp_dev.common.types import SemanticVersion -from cpp_dev.conan.package import get_available_versions -from cpp_dev.dependency.conan.types import ConanPackageReference - - -def test_get_available_versions() -> None: - with patch("cpp_dev.conan.package.conan_list", return_value={ - ConanPackageReference("cpd/1.0.0@official/cppdev"): {}, - ConanPackageReference("cpd/2.0.0@custom/cppdev"): {}, - ConanPackageReference("cpd/3.0.0@official/cppdev"): {}, - }): - assert get_available_versions("official", "cpd") == [ - SemanticVersion("3.0.0"), - SemanticVersion("1.0.0"), - ] \ No newline at end of file diff --git a/src/tests/cpp_dev/dependency/conan/conftest.py b/src/tests/cpp_dev/conftest.py similarity index 94% rename from src/tests/cpp_dev/dependency/conan/conftest.py rename to src/tests/cpp_dev/conftest.py index e911868..0371913 100644 --- a/src/tests/cpp_dev/dependency/conan/conftest.py +++ b/src/tests/cpp_dev/conftest.py @@ -13,15 +13,18 @@ ############################################################################### @pytest.fixture def unused_http_port() -> int: + """Return an unused HTTP port in a pre-defined range.""" for port in range(50000, 50100): if not _is_port_in_use(port): return port raise RuntimeError("No unused HTTP port found") + ############################################################################### # Implementation ### ############################################################################### + def _is_port_in_use(port: int) -> bool: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: return s.connect_ex(("localhost", port)) == 0 diff --git a/src/tests/cpp_dev/dependency/conan/test_command_wrapper.py b/src/tests/cpp_dev/dependency/conan/test_command_wrapper.py index 71038a8..60e9411 100644 --- a/src/tests/cpp_dev/dependency/conan/test_command_wrapper.py +++ b/src/tests/cpp_dev/dependency/conan/test_command_wrapper.py @@ -3,25 +3,26 @@ # This work is licensed under the terms of the BSD-3-Clause license. # For a copy, see . -import json from collections.abc import Generator -from dataclasses import dataclass from pathlib import Path from textwrap import dedent from unittest.mock import AsyncMock, MagicMock, patch import pytest -from cpp_dev.dependency.conan.command_wrapper import (conan_create, +from cpp_dev.dependency.conan.command_wrapper import (ConanCommandException, + ConanSettings, + conan_create, conan_graph_buildorder, conan_list, conan_remote_login, conan_upload) from cpp_dev.dependency.conan.setup import CONAN_REMOTE -from cpp_dev.dependency.conan.types import ConanPackageReference +from cpp_dev.dependency.conan.types import \ + ConanPackageReferenceWithSemanticVersion -from .utils.env import ConanTestEnv, create_conan_env -from .utils.server import ConanServer, launch_conan_server +from .utils.env import ConanTestEnv, ConanTestPackage, create_conan_test_env +from .utils.server import launch_conan_test_server MockType = MagicMock | AsyncMock @@ -30,29 +31,21 @@ def patched_run_command_assert_success() -> Generator[MockType]: with patch("cpp_dev.dependency.conan.command_wrapper.run_command_assert_success") as mock_run_command: yield mock_run_command -def test_conan_remote_login(patched_run_command_assert_success: MockType) -> None: - conan_remote_login(CONAN_REMOTE, "user", "password") - patched_run_command_assert_success.assert_called_once_with( - "conan", - "remote", - "login", - CONAN_REMOTE, - "user", - "-p", - "password", - ) - def test_conan_create(patched_run_command_assert_success: MockType) -> None: - conan_create(Path("package_dir"), "profile") + # todo: this test currently uses a mock, but wil later be changed to test with a real server. + conan_create(Path("package_dir"), "profile", {"compiler": "test", "compiler.cppstd": "c++20"}) patched_run_command_assert_success.assert_called_once_with( "conan", "create", "package_dir", "-pr:a", "profile", + "-s:a", "compiler=test", + "-s:a", "compiler.cppstd=c++20", ) def test_conan_upload(patched_run_command_assert_success: MockType) -> None: - package_ref = ConanPackageReference("cpd/1.0.0@official/cppdev") + # todo: this test currently uses a mock, but wil later be changed to test with a real server. + package_ref = ConanPackageReferenceWithSemanticVersion("cpd/1.0.0@official/cppdev") conan_upload(package_ref, CONAN_REMOTE) patched_run_command_assert_success.assert_called_once_with( "conan", @@ -61,40 +54,57 @@ def test_conan_upload(patched_run_command_assert_success: MockType) -> None: str(package_ref), ) -@dataclass -class ConanTestEnvironment: - server: ConanServer - conan: ConanTestEnv @pytest.fixture -def conan_test_environment(tmp_path: Path, unused_http_port: int) -> Generator[ConanTestEnvironment]: - with launch_conan_server(tmp_path / "server", unused_http_port) as conan_server: - with create_conan_env(tmp_path / "conan", conan_server.http_port) as conan_env: - conan_env.create_and_upload_package(ConanPackageReference("dep/1.0.0@official/cppdev"), []) - conan_env.create_and_upload_package(ConanPackageReference("cpd1/1.0.0@official/cppdev"), []) - conan_env.create_and_upload_package(ConanPackageReference("cpd/1.0.0@official/cppdev"), [ConanPackageReference("dep/1.0.0@official/cppdev")]) - yield ConanTestEnvironment( - server=conan_server, - conan=conan_env - ) - - +def conan_test_environment(tmp_path: Path, unused_http_port: int) -> Generator[ConanTestEnv]: + with launch_conan_test_server(tmp_path, unused_http_port) as server: + TEST_PACKAGES = [ + ConanTestPackage( + ref=ConanPackageReferenceWithSemanticVersion("dep/1.0.0@official/cppdev"), + dependencies=[], + cpp_standard="c++20", + ), + ConanTestPackage( + ref=ConanPackageReferenceWithSemanticVersion("dep/2.0.0@official/cppdev"), + dependencies=[], + cpp_standard="c++20", + ), + ConanTestPackage( + ref=ConanPackageReferenceWithSemanticVersion("cpd/1.0.0@official/cppdev"), + dependencies=[ConanPackageReferenceWithSemanticVersion("dep/1.0.0@official/cppdev")], + cpp_standard="c++20", + ), + ConanTestPackage( + ref=ConanPackageReferenceWithSemanticVersion("cpd1/1.0.0@official/cppdev"), + dependencies=[ConanPackageReferenceWithSemanticVersion("dep/2.0.0@official/cppdev")], + cpp_standard="c++20", + ), + ] + with create_conan_test_env(tmp_path / "conan", server.http_port, TEST_PACKAGES) as conan_test_env: + yield conan_test_env + +@pytest.mark.conan_remote +def test_conan_remote_login(conan_test_environment: ConanTestEnv) -> None: + conan_remote_login(CONAN_REMOTE, conan_test_environment.server.user, conan_test_environment.server.password) + + +@pytest.mark.conan_remote @pytest.mark.usefixtures("conan_test_environment") def test_conan_list() -> None: result = conan_list(CONAN_REMOTE, "cpd") assert len(result) == 1 - assert "cpd/1.0.0@official/cppdev" in result + assert ConanPackageReferenceWithSemanticVersion("cpd/1.0.0@official/cppdev") in result -@pytest.mark.usefixtures("conan_test_environment") -def test_conan_graph_buildorder(tmp_path: Path, conan_test_environment: ConanTestEnvironment) -> None: +@pytest.mark.conan_remote +def test_conan_graph_buildorder(tmp_path: Path, conan_test_environment: ConanTestEnv) -> None: conanfile_path = tmp_path / "conanfile.txt" conanfile_path.write_text(dedent(""" [requires] cpd/1.0.0@official/cppdev """) ) - graph_build_order = conan_graph_buildorder(conanfile_path, conan_test_environment.conan.profile) + graph_build_order = conan_graph_buildorder(conanfile_path, conan_test_environment.profile, conan_test_environment.construct_conan_settings()) assert len(graph_build_order.order) == 2 assert len(graph_build_order.order[0]) == 1 dep_recipe = graph_build_order.order[0][0] @@ -107,3 +117,28 @@ def test_conan_graph_buildorder(tmp_path: Path, conan_test_environment: ConanTes assert cpd_recipe.depends[0].startswith("dep/1.0.0@official/cppdev") +@pytest.mark.conan_remote +def test_conan_graph_buildorder_dependency_does_not_exist(tmp_path: Path, conan_test_environment: ConanTestEnv) -> None: + conanfile_path = tmp_path / "conanfile.txt" + conanfile_path.write_text(dedent(""" + [requires] + cpd/0.0.0@official/cppdev + """) + ) + + with pytest.raises(ConanCommandException, match="unable to find package") as e: + conan_graph_buildorder(conanfile_path, conan_test_environment.profile, conan_test_environment.construct_conan_settings()) + + +@pytest.mark.conan_remote +def test_conan_graph_buildorder_multiple_dependencies(tmp_path: Path, conan_test_environment: ConanTestEnv) -> None: + conanfile_path = tmp_path / "conanfile.txt" + conanfile_path.write_text(dedent(""" + [requires] + cpd/[>=0.0.0]@official/cppdev + cpd1/[<2.0.0]@official/cppdev + """) + ) + + with pytest.raises(ConanCommandException, match="version conflict") as e: + conan_graph_buildorder(conanfile_path, conan_test_environment.profile, conan_test_environment.construct_conan_settings()) \ No newline at end of file diff --git a/src/tests/cpp_dev/dependency/conan/test_provider.py b/src/tests/cpp_dev/dependency/conan/test_provider.py new file mode 100644 index 0000000..77471dc --- /dev/null +++ b/src/tests/cpp_dev/dependency/conan/test_provider.py @@ -0,0 +1,81 @@ +# Copyright (c) 2024 Andi Hellmund. All rights reserved. + +# This work is licensed under the terms of the BSD-3-Clause license. +# For a copy, see . + +from collections.abc import Generator +from dataclasses import dataclass +from pathlib import Path + +import pytest + +from cpp_dev.common.types import CppStandard +from cpp_dev.common.version import SemanticVersion +from cpp_dev.dependency.conan.command_wrapper import ConanSettings +from cpp_dev.dependency.conan.provider import ConanDependencyProvider +from cpp_dev.dependency.conan.types import \ + ConanPackageReferenceWithSemanticVersion +from cpp_dev.dependency.provider import DependencyIdentifier +from cpp_dev.dependency.specifier import DependencySpecifier +from tests.cpp_dev.dependency.conan.utils.env import (ConanTestEnv, + ConanTestPackage, + create_conan_test_env) + + +@pytest.fixture +def conan_test_environment(tmp_path: Path, unused_http_port: int) -> Generator[ConanTestEnv]: + TEST_PACKAGES = [ + ConanTestPackage( + ref=ConanPackageReferenceWithSemanticVersion("subdep/1.0.0@official/cppdev"), + dependencies=[], + cpp_standard="c++20", + ), + ConanTestPackage( + ref=ConanPackageReferenceWithSemanticVersion("dep/1.0.0@official/cppdev"), + dependencies=[ + ConanPackageReferenceWithSemanticVersion("subdep/1.0.0@official/cppdev") + ], + cpp_standard="c++20", + ), + ConanTestPackage( + ref=ConanPackageReferenceWithSemanticVersion("cpd/1.0.0@official/cppdev"), + dependencies=[], + cpp_standard="c++20", + ), + ConanTestPackage( + ref=ConanPackageReferenceWithSemanticVersion("cpd/2.0.0@custom/cppdev"), + dependencies=[], + cpp_standard="c++20", + ), + ConanTestPackage( + ref=ConanPackageReferenceWithSemanticVersion("cpd/3.0.0@official/cppdev"), + dependencies=[ + ConanPackageReferenceWithSemanticVersion("dep/1.0.0@official/cppdev") + ], + cpp_standard="c++20", + ), + ] + with create_conan_test_env(tmp_path, unused_http_port, TEST_PACKAGES) as conan_test_env: + yield conan_test_env + + +@pytest.mark.conan_remote +def test_get_available_versions(conan_test_environment: ConanTestEnv) -> None: + provider = ConanDependencyProvider(conan_test_environment.conan_home_dir, conan_test_environment.profile, conan_test_environment.construct_conan_settings()) + assert provider.fetch_versions("official", "cpd") == [ + SemanticVersion("3.0.0"), + SemanticVersion("1.0.0"), + ] + + +@pytest.mark.conan_remote +def test_collect_dependency_hull(conan_test_environment: ConanTestEnv) -> None: + provider = ConanDependencyProvider(conan_test_environment.conan_home_dir, conan_test_environment.profile, conan_test_environment.construct_conan_settings()) + deps = [ + DependencySpecifier("official/cpd[>=3.0.0]"), + ] + dependencies = provider.collect_dependency_hull(deps) + assert len(dependencies) == 3 + assert DependencyIdentifier.from_str("official/cpd/3.0.0") in dependencies + assert DependencyIdentifier.from_str("official/dep/1.0.0") in dependencies + assert DependencyIdentifier.from_str("official/subdep/1.0.0") in dependencies \ No newline at end of file diff --git a/src/tests/cpp_dev/dependency/conan/test_setup.py b/src/tests/cpp_dev/dependency/conan/test_setup.py index de5cb6e..37bf7ef 100644 --- a/src/tests/cpp_dev/dependency/conan/test_setup.py +++ b/src/tests/cpp_dev/dependency/conan/test_setup.py @@ -18,4 +18,4 @@ def test_initialize_conan(tmp_path: Path) -> None: assert (tmp_path / "remotes.json").exists() assert (tmp_path / "settings.yml").exists() - assert (tmp_path / "profiles" / "ubuntu-24.04").exists() + assert (tmp_path / "profiles" / "ubuntu-24.04-x86_64").exists() diff --git a/src/tests/cpp_dev/dependency/conan/test_types.py b/src/tests/cpp_dev/dependency/conan/test_types.py index 89c553e..e5c4a73 100644 --- a/src/tests/cpp_dev/dependency/conan/test_types.py +++ b/src/tests/cpp_dev/dependency/conan/test_types.py @@ -1,7 +1,8 @@ import pytest from cpp_dev.common.version import SemanticVersion -from cpp_dev.dependency.conan.types import ConanPackageReference +from cpp_dev.dependency.conan.types import \ + ConanPackageReferenceWithSemanticVersion @pytest.mark.parametrize("invalid_ref", [ @@ -11,12 +12,21 @@ ]) def test_conan_package_reference_invalid(invalid_ref): with pytest.raises(ValueError, match="Invalid Conan package reference:"): - ConanPackageReference(invalid_ref) + ConanPackageReferenceWithSemanticVersion(invalid_ref) def test_conan_package_reference_valid() -> None: - package_ref = ConanPackageReference("name/1.2.3@user/channel") + package_ref = ConanPackageReferenceWithSemanticVersion("name/1.2.3@user/channel") assert package_ref.name == "name" assert package_ref.version == SemanticVersion("1.2.3") assert package_ref.user == "user" assert package_ref.channel == "channel" + + +def test_conan_package_reference_from_raw_string_with_revision() -> None: + package_ref = ConanPackageReferenceWithSemanticVersion.from_raw_string_with_revision("name/1.2.3@user/channel#revision") + + assert package_ref.name == "name" + assert package_ref.version == SemanticVersion("1.2.3") + assert package_ref.user == "user" + assert package_ref.channel == "channel" \ No newline at end of file diff --git a/src/tests/cpp_dev/dependency/conan/utils/env.py b/src/tests/cpp_dev/dependency/conan/utils/env.py index 3092c5e..9b69757 100644 --- a/src/tests/cpp_dev/dependency/conan/utils/env.py +++ b/src/tests/cpp_dev/dependency/conan/utils/env.py @@ -4,54 +4,122 @@ # For a copy, see . import json -from collections.abc import Generator +from collections.abc import Generator, Mapping from contextlib import contextmanager +from dataclasses import dataclass from pathlib import Path from textwrap import dedent +from cpp_dev.common.types import CppStandard from cpp_dev.common.utils import ensure_dir_exists -from cpp_dev.dependency.conan.command_wrapper import conan_create, conan_upload +from cpp_dev.dependency.conan.command_wrapper import (ConanSettings, + conan_create, + conan_upload) from cpp_dev.dependency.conan.setup import CONAN_REMOTE, initialize_conan -from cpp_dev.dependency.conan.types import ConanPackageReference +from cpp_dev.dependency.conan.types import \ + ConanPackageReferenceWithSemanticVersion from cpp_dev.dependency.conan.utils import conan_env +from tests.cpp_dev.dependency.conan.utils.server import ( + ConanServer, launch_conan_test_server) ############################################################################### # Public API ### ############################################################################### +FileSpec = Mapping[Path, str] + +@dataclass +class ConanTestPackage: + ref: ConanPackageReferenceWithSemanticVersion + dependencies: list[ConanPackageReferenceWithSemanticVersion] + cpp_standard: CppStandard + bin_files: FileSpec | None = None + lib_files: FileSpec | None = None + include_files: FileSpec | None = None + + class ConanTestEnv: """A Conan environment for testing.""" - def __init__(self, base_dir: Path, profile: str) -> None: - self._base_dir = base_dir / ".conan_test_env" - ensure_dir_exists(self._base_dir) + def __init__(self, conan_home_dir: Path, profile: str, server: ConanServer, compiler: str, cppstd: CppStandard) -> None: + self._conan_home_dir = conan_home_dir + self._package_dir = conan_home_dir / ".conan_package_creation" + ensure_dir_exists(self._package_dir) self._profile = profile + self._server = server + self._compiler = compiler + self._cppstd = cppstd - def create_and_upload_package(self, ref: ConanPackageReference, dependencies: list[ConanPackageReference]) -> None: + def create_and_upload_packages(self, packages: list[ConanTestPackage]) -> None: """Create and upload a Conan package for testing.""" - _create_and_upload_conan_package(self._base_dir, ref, dependencies, self._profile) + settings = self.construct_conan_settings() + for package in packages: + _create_and_upload_conan_package(self._package_dir, package, self._profile, settings) + + @property + def conan_home_dir(self) -> Path: + """Return the base directory of the Conan environment.""" + return self._conan_home_dir @property def profile(self) -> str: """Return the profile used for testing.""" return self._profile + + @property + def server(self) -> ConanServer: + """Return the server used for testing.""" + return self._server + + @property + def compiler(self) -> str: + """Return the compiler used for testing.""" + return self._compiler + + @property + def cppstd(self) -> CppStandard: + """Return the C++ standard used for testing.""" + return self._cppstd + + def construct_conan_settings(self) -> ConanSettings: + """Construct the additional Conan settings for the Conan commands.""" + return { + "compiler": self._compiler, + "compiler.cppstd": self._cppstd, + } @contextmanager -def create_conan_env(conan_dir: Path, server_http_port: int) -> Generator[ConanTestEnv]: +def create_conan_test_env(base_dir: Path, server_http_port: int, packages: list[ConanTestPackage]) -> Generator[ConanTestEnv]: """Create a Conan environment for testing.""" - ensure_dir_exists(conan_dir) - source_config_path, profile = _create_conan_source_config(conan_dir, server_http_port) - initialize_conan(conan_dir, source_config_path) - with conan_env(conan_dir): - yield ConanTestEnv(conan_dir, profile) + + # create the home for the conan server + conan_server_dir = base_dir / "server" + ensure_dir_exists(conan_server_dir) + with launch_conan_test_server(conan_server_dir, server_http_port) as server: + # create the home for the conan client + conan_home_dir = base_dir / "conan_home" + ensure_dir_exists(conan_home_dir) + source_config_path, attributes = _create_conan_source_config(conan_home_dir, server_http_port) + initialize_conan(conan_home_dir, source_config_path) + + with conan_env(conan_home_dir): + conan_test_env = ConanTestEnv(conan_home_dir, attributes.profile, server, attributes.compiler, attributes.cppstd) + conan_test_env.create_and_upload_packages(packages) + yield conan_test_env ############################################################################### # Implementation ### ############################################################################### -def _create_conan_source_config(conan_dir: Path, server_http_port: int) -> tuple[Path, str]: +@dataclass +class ConanSourceConfigAttributes: + profile: str + compiler: str + cppstd: CppStandard + +def _create_conan_source_config(conan_dir: Path, server_http_port: int) -> tuple[Path, ConanSourceConfigAttributes]: source_config_path = conan_dir / ".source_config" ensure_dir_exists(source_config_path) @@ -68,15 +136,18 @@ def _create_conan_source_config(conan_dir: Path, server_http_port: int) -> tuple """ )) + COMPILER = "test" + CPPSTD = "c++20" settings_path = source_config_path / "settings.yml" settings_path.write_text(dedent( - """ + f""" os: Linux: distro: ["test"] arch: [test] compiler: - test: + {COMPILER}: + cppstd: [{CPPSTD}] build_type: [Test] """ )) @@ -92,16 +163,20 @@ def _create_conan_source_config(conan_dir: Path, server_http_port: int) -> tuple ] })) - return source_config_path, PROFILE_NAME + return source_config_path, ConanSourceConfigAttributes( + profile=PROFILE_NAME, + compiler=COMPILER, + cppstd=CPPSTD, + ) -def _create_and_upload_conan_package(base_dir: Path, ref: ConanPackageReference, dependencies: list[ConanPackageReference], profile: str) -> None: - package_dir = base_dir / f"{ref.name}_{ref.version}" +def _create_and_upload_conan_package(base_dir: Path, package: ConanTestPackage, profile: str, settings: ConanSettings) -> None: + package_dir = base_dir / f"{package.ref.name}_{package.ref.version}" ensure_dir_exists(package_dir) conanfile_path = package_dir / "conanfile.py" - requirements = ",".join([f"\"{dep}\"" for dep in dependencies]) + requirements = ",".join([f"\"{dep}\"" for dep in package.dependencies]) conanfile_path.write_text(dedent( """ from conan import ConanFile @@ -112,13 +187,19 @@ class TestConan(ConanFile): channel = "cppdev" version = "{version}" + settings = "compiler" + {requirements} + + def configure(self): + self.settings.rm_safe("compiler.libcxx") + """.format( - name=ref.name, - user=ref.user, - version=ref.version, - requirements=f"requires = {requirements}" if dependencies else "" + name=package.ref.name, + user=package.ref.user, + version=package.ref.version, + requirements=f"requires = {requirements}" if package.dependencies else "", ) )) - conan_create(package_dir, profile) - conan_upload(ref, CONAN_REMOTE) \ No newline at end of file + conan_create(package_dir, profile, settings) + conan_upload(package.ref, CONAN_REMOTE) \ No newline at end of file diff --git a/src/tests/cpp_dev/dependency/conan/utils/server.py b/src/tests/cpp_dev/dependency/conan/utils/server.py index 07eccdf..4579db6 100644 --- a/src/tests/cpp_dev/dependency/conan/utils/server.py +++ b/src/tests/cpp_dev/dependency/conan/utils/server.py @@ -32,8 +32,8 @@ def compose_url(self) -> str: return f"http://{self.hostname}:{self.http_port}" @contextmanager -def launch_conan_server(server_dir: Path, http_port: int) -> Generator[ConanServer]: - """Launch a Conan server.""" +def launch_conan_test_server(server_dir: Path, http_port: int) -> Generator[ConanServer]: + """Launch a Conan server for testing.""" connection_params = _create_conan_server_config(server_dir, http_port) process = _launch_conan_server(server_dir) _wait_for_server_start(connection_params) diff --git a/src/tests/cpp_dev/dependency/conan/utils/test_env.py b/src/tests/cpp_dev/dependency/conan/utils/test_env.py index d7f047a..0dbc59b 100644 --- a/src/tests/cpp_dev/dependency/conan/utils/test_env.py +++ b/src/tests/cpp_dev/dependency/conan/utils/test_env.py @@ -7,16 +7,27 @@ from cpp_dev.dependency.conan.command_wrapper import conan_list from cpp_dev.dependency.conan.setup import CONAN_REMOTE -from cpp_dev.dependency.conan.types import ConanPackageReference +from cpp_dev.dependency.conan.types import \ + ConanPackageReferenceWithSemanticVersion +from tests.cpp_dev.dependency.conan.utils.server import \ + launch_conan_test_server -from .env import create_conan_env -from .server import launch_conan_server +from .env import ConanTestPackage, create_conan_test_env +TEST_PACKAGES = [ + ConanTestPackage( + ref=ConanPackageReferenceWithSemanticVersion("dep/1.0.0@official/cppdev"), + dependencies=[], + cpp_standard="c++20", + ), + ConanTestPackage( + ref=ConanPackageReferenceWithSemanticVersion("test/1.0.0@official/cppdev"), + dependencies=[ConanPackageReferenceWithSemanticVersion("dep/1.0.0@official/cppdev")], + cpp_standard="c++20", + ), +] def test_create_conan_env(tmp_path: Path, unused_http_port: int) -> None: - with launch_conan_server(tmp_path / "server", unused_http_port) as conan_server: - with create_conan_env(tmp_path / "conan", conan_server.http_port) as conan_env: - conan_env.create_and_upload_package(ConanPackageReference("dep/1.0.0@official/cppdev"), []) - conan_env.create_and_upload_package(ConanPackageReference("test/1.0.0@official/cppdev"), [ConanPackageReference("dep/1.0.0@official/cppdev")]) - result = conan_list(CONAN_REMOTE, "test") - assert "test/1.0.0@official/cppdev" in result \ No newline at end of file + with create_conan_test_env(tmp_path, unused_http_port, TEST_PACKAGES) as conan_env: + result = conan_list(CONAN_REMOTE, "test") + assert ConanPackageReferenceWithSemanticVersion("test/1.0.0@official/cppdev") in result \ No newline at end of file diff --git a/src/tests/cpp_dev/dependency/conan/utils/test_server.py b/src/tests/cpp_dev/dependency/conan/utils/test_server.py index d6d3cf4..5752db1 100644 --- a/src/tests/cpp_dev/dependency/conan/utils/test_server.py +++ b/src/tests/cpp_dev/dependency/conan/utils/test_server.py @@ -7,10 +7,10 @@ import requests -from .server import launch_conan_server +from .server import launch_conan_test_server def test_launch_conan_server(tmp_path: Path, unused_http_port: int) -> None: - with launch_conan_server(tmp_path / "server", unused_http_port) as conan_server: + with launch_conan_test_server(tmp_path / "server", unused_http_port) as conan_server: response = requests.get(conan_server.compose_url() + "/v1/ping", timeout=2) assert response.status_code == 200 \ No newline at end of file diff --git a/src/tests/cpp_dev/project/dependency/__init__.py b/src/tests/cpp_dev/project/dependency/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/tests/cpp_dev/project/dependency/_test_utils.py b/src/tests/cpp_dev/project/dependency/_test_utils.py deleted file mode 100644 index bf09122..0000000 --- a/src/tests/cpp_dev/project/dependency/_test_utils.py +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright (c) 2024 Andi Hellmund. All rights reserved. - -# This work is licensed under the terms of the BSD-3-Clause license. -# For a copy, see . - -from collections.abc import Mapping -from unittest.mock import patch - -from cpp_dev.dependency.conan.types import ConanPackageReference -from cpp_dev.dependency.types import PackageDependency -from cpp_dev.project.dependency.utils import refine_package_dependencies - - -def conan_list_side_effect(_remote: str, name: str) -> Mapping[ConanPackageReference, dict]: - if name == "cpd": - return { - ConanPackageReference("cpd/1.0.0@official/cppdev"): {}, - ConanPackageReference("cpd/2.0.0@official/cppdev"): {}, - } - if name == "cpd2": - return { - ConanPackageReference("cpd2/3.0.0@official/cppdev"): {}, - ConanPackageReference("cpd2/3.0.0@official/cppdev"): {}, - } - if name == "other": - return { - ConanPackageReference("other/2.0.0@custom/cppdev"): {}, - } - return {} - - -def test_refine_package_dependencies() -> None: - with patch("cpp_dev.conan.package.conan_list", side_effect=conan_list_side_effect): - refined_deps = refine_package_dependencies( - [ - PackageDependency("cpd"), - PackageDependency("custom/other[latest]"), - PackageDependency("cpd2[3.0.0]"), - ] - ) - assert refined_deps == [ - PackageDependency("official/cpd[>=2.0.0]"), - PackageDependency("custom/other[>=2.0.0]"), - PackageDependency("official/cpd2[3.0.0]"), - ] diff --git a/src/tests/cpp_dev/project/test_config.py b/src/tests/cpp_dev/project/test_config.py index b9e7012..b024289 100644 --- a/src/tests/cpp_dev/project/test_config.py +++ b/src/tests/cpp_dev/project/test_config.py @@ -16,6 +16,7 @@ create_project_config, load_project_config, update_dependencies, + validate_dependencies, ) from cpp_dev.project.path_composition import compose_project_config_file @@ -25,7 +26,7 @@ def project_config() -> ProjectConfig: return ProjectConfig( name="test", version=SemanticVersion("0.1.0"), - std="c++17", + std="c++20", author="author", license="license", description="description", @@ -83,3 +84,34 @@ def test_update_dependencies( assert new_config.get_dependencies(unchanged_type) == project_setup.project_config.get_dependencies( unchanged_type ) + + +def test_validate_dependencies_valid() -> None: + valid_config = ProjectConfig( + name="test", + version=SemanticVersion("0.1.0"), + std="c++20", + author="author", + license="license", + description="description", + dependencies=[DependencySpecifier("dep1"), DependencySpecifier("dep2")], + dev_dependencies=[DependencySpecifier("dev1")], + cpd_dependencies=[DependencySpecifier("cpd1")], + ) + validate_dependencies(valid_config) # Should not raise an exception + + +def test_validate_dependencies_invalid() -> None: + invalid_config = ProjectConfig( + name="test", + version=SemanticVersion("0.1.0"), + std="c++20", + author="author", + license="license", + description="description", + dependencies=[DependencySpecifier("dep1")], + dev_dependencies=[DependencySpecifier("dep1")], # Duplicate dependency + cpd_dependencies=[DependencySpecifier("cpd1")], + ) + with pytest.raises(ValueError, match="Dependency 'dep1' is defined multiple times."): + validate_dependencies(invalid_config) diff --git a/src/tests/cpp_dev/project/test_core.py b/src/tests/cpp_dev/project/test_core.py index d4dda43..22a0000 100644 --- a/src/tests/cpp_dev/project/test_core.py +++ b/src/tests/cpp_dev/project/test_core.py @@ -4,25 +4,28 @@ # For a copy, see . +from collections.abc import Mapping from pathlib import Path import pytest from cpp_dev.common.version import SemanticVersion -from cpp_dev.dependency.provider import Dependency, DependencyIdentifier, DependencyProvider +from cpp_dev.dependency.conan.types import ConanPackageReferenceWithSemanticVersion +from cpp_dev.dependency.provider import DependencyIdentifier, DependencyProvider +from cpp_dev.dependency.specifier import DependencySpecifier from cpp_dev.project.config import ProjectConfig, load_project_config -from cpp_dev.project.core import setup_project +from cpp_dev.project.core import _refine_package_dependencies, setup_project from cpp_dev.project.lockfile import load_lock_file from cpp_dev.project.path_composition import compose_project_config_file, compose_project_lock_file -from tests.cpp_dev.project.utils.artificial_dependency_provider import ArtificialDependencyProvider +from tests.cpp_dev.project.utils.artificial_dependency_provider import ArtificialDependencyProvider, Dependency @pytest.fixture def dep_provider() -> DependencyProvider: return ArtificialDependencyProvider( dependencies=[ - Dependency(id=DependencyIdentifier.from_str("official/llvm/1.0.0"), cpp_standard="c++17", deps=[]), - Dependency(id=DependencyIdentifier.from_str("official/gtest/1.0.0"), cpp_standard="c++17", deps=[]), + Dependency(id=DependencyIdentifier.from_str("official/llvm/1.0.0"), cpp_standard="c++20", deps=[]), + Dependency(id=DependencyIdentifier.from_str("official/gtest/1.0.0"), cpp_standard="c++20", deps=[]), ] ) @@ -31,7 +34,7 @@ def test_setup_project(tmp_path: Path, dep_provider: DependencyProvider) -> None project_config = ProjectConfig( name="test_package", version=SemanticVersion("1.0.0"), - std="c++17", + std="c++20", author="author", license="license", description="description", @@ -61,3 +64,37 @@ def test_setup_project(tmp_path: Path, dep_provider: DependencyProvider) -> None assert (project.project_dir / "include" / project_config.name / f"{project_config.name}.hpp").exists() assert (project.project_dir / "src" / f"{project_config.name}.cpp").exists() assert (project.project_dir / "src" / f"{project_config.name}.test.cpp").exists() + + +def conan_list_side_effect(_remote: str, name: str) -> Mapping[ConanPackageReferenceWithSemanticVersion, dict]: + if name == "cpd": + return { + ConanPackageReferenceWithSemanticVersion("cpd/1.0.0@official/cppdev"): {}, + ConanPackageReferenceWithSemanticVersion("cpd/2.0.0@official/cppdev"): {}, + } + if name == "cpd2": + return { + ConanPackageReferenceWithSemanticVersion("cpd2/3.0.0@official/cppdev"): {}, + ConanPackageReferenceWithSemanticVersion("cpd2/3.0.0@official/cppdev"): {}, + } + if name == "other": + return { + ConanPackageReferenceWithSemanticVersion("other/2.0.0@custom/cppdev"): {}, + } + return {} + + +def test_refine_package_dependencies(dep_provider: DependencyProvider) -> None: + refined_deps = _refine_package_dependencies( + dep_provider, + [ + DependencySpecifier("llvm"), + DependencySpecifier("official/llvm[latest]"), + DependencySpecifier("gtest[3.0.0]"), + ], + ) + assert refined_deps == [ + DependencySpecifier("official/llvm[>=1.0.0]"), + DependencySpecifier("official/llvm[>=1.0.0]"), + DependencySpecifier("official/gtest[3.0.0]"), + ] diff --git a/src/tests/cpp_dev/project/utils/artificial_dependency_provider.py b/src/tests/cpp_dev/project/utils/artificial_dependency_provider.py index 5609c77..8c9f53c 100644 --- a/src/tests/cpp_dev/project/utils/artificial_dependency_provider.py +++ b/src/tests/cpp_dev/project/utils/artificial_dependency_provider.py @@ -5,12 +5,11 @@ from __future__ import annotations -from abc import ABC, abstractmethod from dataclasses import dataclass from cpp_dev.common.types import CppStandard from cpp_dev.common.version import SemanticVersion -from cpp_dev.dependency.provider import Dependency, DependencyIdentifier, DependencyProvider +from cpp_dev.dependency.provider import DependencyIdentifier, DependencyProvider from cpp_dev.dependency.specifier import DependencySpecifier ############################################################################### @@ -18,11 +17,20 @@ ############################################################################### +@dataclass +class Dependency: + """Attributes of a dependency.""" + + id: DependencyIdentifier + cpp_standard: CppStandard + deps: list[DependencyIdentifier] + + class ArtificialDependencyProvider(DependencyProvider): """Test implementation of DependencyProvider for testing purposes.""" def __init__(self, dependencies: list[Dependency]) -> None: - self._dependencies = {str(d.id): d for d in dependencies} + self._dependencies = {d.id: d for d in dependencies} _assert_dependencies_are_complete(self._dependencies) def fetch_versions(self, repository: str, name: str) -> list[SemanticVersion]: @@ -36,11 +44,13 @@ def fetch_versions(self, repository: str, name: str) -> list[SemanticVersion]: raise ValueError(f"No available versions for package {name} at repository {repository}.") return sorted(available_versions, reverse=True) - def collect_dependency_hull(self, deps: list[DependencySpecifier]) -> list[Dependency]: + def collect_dependency_hull(self, _deps: list[DependencySpecifier]) -> set[DependencyIdentifier]: """Collect the dependency hull for a list of dependencies.""" + return set() - def install_dependencies(self, deps: list[DependencySpecifier]) -> list[DependencySpecifier]: + def install_dependencies(self, _deps: list[DependencySpecifier]) -> list[DependencySpecifier]: """Install the dependencies represented by the list of dependency specifiers.""" + return [] ############################################################################### diff --git a/src/tests/cpp_dev/project/utils/test_artificial_dependency_provider.py b/src/tests/cpp_dev/project/utils/test_artificial_dependency_provider.py index 68a8549..440730a 100644 --- a/src/tests/cpp_dev/project/utils/test_artificial_dependency_provider.py +++ b/src/tests/cpp_dev/project/utils/test_artificial_dependency_provider.py @@ -5,16 +5,16 @@ from cpp_dev.common.version import SemanticVersion -from cpp_dev.dependency.provider import Dependency, DependencyIdentifier -from tests.cpp_dev.project.utils.artificial_dependency_provider import ArtificialDependencyProvider +from cpp_dev.dependency.provider import DependencyIdentifier +from tests.cpp_dev.project.utils.artificial_dependency_provider import ArtificialDependencyProvider, Dependency def test_available_versions() -> None: provider = ArtificialDependencyProvider( [ - Dependency(id=DependencyIdentifier.from_str("official/gtest/1.0.0"), cpp_standard="c++17", deps=[]), - Dependency(id=DependencyIdentifier.from_str("official/gtest/1.10.0"), cpp_standard="c++17", deps=[]), - Dependency(id=DependencyIdentifier.from_str("custom/gtest/1.11.0"), cpp_standard="c++17", deps=[]), + Dependency(id=DependencyIdentifier.from_str("official/gtest/1.0.0"), cpp_standard="c++20", deps=[]), + Dependency(id=DependencyIdentifier.from_str("official/gtest/1.10.0"), cpp_standard="c++20", deps=[]), + Dependency(id=DependencyIdentifier.from_str("custom/gtest/1.11.0"), cpp_standard="c++20", deps=[]), ] ) assert provider.fetch_versions("official", "gtest") == [ diff --git a/src/tests/cpp_dev/tool/_test_init.py b/src/tests/cpp_dev/tool/test_init.py similarity index 95% rename from src/tests/cpp_dev/tool/_test_init.py rename to src/tests/cpp_dev/tool/test_init.py index 7a4bcba..4ad97bc 100644 --- a/src/tests/cpp_dev/tool/_test_init.py +++ b/src/tests/cpp_dev/tool/test_init.py @@ -18,7 +18,7 @@ @pytest.fixture(autouse=True) def conan_remote_login() -> object: """Mock the conan user and password setting function.""" - with patch("cpp_dev.conan.setup.conan_remote_login", return_value=None) as mock: + with patch("cpp_dev.dependency.conan.setup.conan_remote_login", return_value=None) as mock: yield mock diff --git a/src/tests/cpp_dev/tool/_test_version.py b/src/tests/cpp_dev/tool/test_version.py similarity index 100% rename from src/tests/cpp_dev/tool/_test_version.py rename to src/tests/cpp_dev/tool/test_version.py diff --git a/uv.lock b/uv.lock index c987a54..fdc4dd3 100644 --- a/uv.lock +++ b/uv.lock @@ -39,41 +39,37 @@ wheels = [ [[package]] name = "charset-normalizer" -version = "3.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f2/4f/e1808dc01273379acc506d18f1504eb2d299bd4131743b9fc54d7be4df1e/charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e", size = 106620 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d3/0b/4b7a70987abf9b8196845806198975b6aab4ce016632f817ad758a5aa056/charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6", size = 194445 }, - { url = "https://files.pythonhosted.org/packages/50/89/354cc56cf4dd2449715bc9a0f54f3aef3dc700d2d62d1fa5bbea53b13426/charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf", size = 125275 }, - { url = "https://files.pythonhosted.org/packages/fa/44/b730e2a2580110ced837ac083d8ad222343c96bb6b66e9e4e706e4d0b6df/charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db", size = 119020 }, - { url = "https://files.pythonhosted.org/packages/9d/e4/9263b8240ed9472a2ae7ddc3e516e71ef46617fe40eaa51221ccd4ad9a27/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1", size = 139128 }, - { url = "https://files.pythonhosted.org/packages/6b/e3/9f73e779315a54334240353eaea75854a9a690f3f580e4bd85d977cb2204/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03", size = 149277 }, - { url = "https://files.pythonhosted.org/packages/1a/cf/f1f50c2f295312edb8a548d3fa56a5c923b146cd3f24114d5adb7e7be558/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284", size = 142174 }, - { url = "https://files.pythonhosted.org/packages/16/92/92a76dc2ff3a12e69ba94e7e05168d37d0345fa08c87e1fe24d0c2a42223/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15", size = 143838 }, - { url = "https://files.pythonhosted.org/packages/a4/01/2117ff2b1dfc61695daf2babe4a874bca328489afa85952440b59819e9d7/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8", size = 146149 }, - { url = "https://files.pythonhosted.org/packages/f6/9b/93a332b8d25b347f6839ca0a61b7f0287b0930216994e8bf67a75d050255/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2", size = 140043 }, - { url = "https://files.pythonhosted.org/packages/ab/f6/7ac4a01adcdecbc7a7587767c776d53d369b8b971382b91211489535acf0/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719", size = 148229 }, - { url = "https://files.pythonhosted.org/packages/9d/be/5708ad18161dee7dc6a0f7e6cf3a88ea6279c3e8484844c0590e50e803ef/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631", size = 151556 }, - { url = "https://files.pythonhosted.org/packages/5a/bb/3d8bc22bacb9eb89785e83e6723f9888265f3a0de3b9ce724d66bd49884e/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b", size = 149772 }, - { url = "https://files.pythonhosted.org/packages/f7/fa/d3fc622de05a86f30beea5fc4e9ac46aead4731e73fd9055496732bcc0a4/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565", size = 144800 }, - { url = "https://files.pythonhosted.org/packages/9a/65/bdb9bc496d7d190d725e96816e20e2ae3a6fa42a5cac99c3c3d6ff884118/charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7", size = 94836 }, - { url = "https://files.pythonhosted.org/packages/3e/67/7b72b69d25b89c0b3cea583ee372c43aa24df15f0e0f8d3982c57804984b/charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9", size = 102187 }, - { url = "https://files.pythonhosted.org/packages/f3/89/68a4c86f1a0002810a27f12e9a7b22feb198c59b2f05231349fbce5c06f4/charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114", size = 194617 }, - { url = "https://files.pythonhosted.org/packages/4f/cd/8947fe425e2ab0aa57aceb7807af13a0e4162cd21eee42ef5b053447edf5/charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed", size = 125310 }, - { url = "https://files.pythonhosted.org/packages/5b/f0/b5263e8668a4ee9becc2b451ed909e9c27058337fda5b8c49588183c267a/charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250", size = 119126 }, - { url = "https://files.pythonhosted.org/packages/ff/6e/e445afe4f7fda27a533f3234b627b3e515a1b9429bc981c9a5e2aa5d97b6/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920", size = 139342 }, - { url = "https://files.pythonhosted.org/packages/a1/b2/4af9993b532d93270538ad4926c8e37dc29f2111c36f9c629840c57cd9b3/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64", size = 149383 }, - { url = "https://files.pythonhosted.org/packages/fb/6f/4e78c3b97686b871db9be6f31d64e9264e889f8c9d7ab33c771f847f79b7/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23", size = 142214 }, - { url = "https://files.pythonhosted.org/packages/2b/c9/1c8fe3ce05d30c87eff498592c89015b19fade13df42850aafae09e94f35/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc", size = 144104 }, - { url = "https://files.pythonhosted.org/packages/ee/68/efad5dcb306bf37db7db338338e7bb8ebd8cf38ee5bbd5ceaaaa46f257e6/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d", size = 146255 }, - { url = "https://files.pythonhosted.org/packages/0c/75/1ed813c3ffd200b1f3e71121c95da3f79e6d2a96120163443b3ad1057505/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88", size = 140251 }, - { url = "https://files.pythonhosted.org/packages/7d/0d/6f32255c1979653b448d3c709583557a4d24ff97ac4f3a5be156b2e6a210/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90", size = 148474 }, - { url = "https://files.pythonhosted.org/packages/ac/a0/c1b5298de4670d997101fef95b97ac440e8c8d8b4efa5a4d1ef44af82f0d/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b", size = 151849 }, - { url = "https://files.pythonhosted.org/packages/04/4f/b3961ba0c664989ba63e30595a3ed0875d6790ff26671e2aae2fdc28a399/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d", size = 149781 }, - { url = "https://files.pythonhosted.org/packages/d8/90/6af4cd042066a4adad58ae25648a12c09c879efa4849c705719ba1b23d8c/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482", size = 144970 }, - { url = "https://files.pythonhosted.org/packages/cc/67/e5e7e0cbfefc4ca79025238b43cdf8a2037854195b37d6417f3d0895c4c2/charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67", size = 94973 }, - { url = "https://files.pythonhosted.org/packages/65/97/fc9bbc54ee13d33dc54a7fcf17b26368b18505500fc01e228c27b5222d80/charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b", size = 102308 }, - { url = "https://files.pythonhosted.org/packages/bf/9b/08c0432272d77b04803958a4598a51e2a4b51c06640af8b8f0f908c18bf2/charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079", size = 49446 }, +version = "3.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105 }, + { url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404 }, + { url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423 }, + { url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184 }, + { url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268 }, + { url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601 }, + { url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098 }, + { url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520 }, + { url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852 }, + { url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488 }, + { url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192 }, + { url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550 }, + { url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785 }, + { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698 }, + { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162 }, + { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263 }, + { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966 }, + { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992 }, + { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162 }, + { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972 }, + { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095 }, + { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668 }, + { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073 }, + { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732 }, + { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391 }, + { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702 }, + { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 }, ] [[package]] @@ -91,7 +87,7 @@ version = "2.11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama" }, - { name = "distro", marker = "platform_system == "FreeBSD" or sys_platform == "linux"" }, + { name = "distro", marker = "platform_system == 'FreeBSD' or sys_platform == 'linux'" }, { name = "fasteners" }, { name = "jinja2" }, { name = "patch-ng" }, @@ -109,7 +105,7 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "bottle" }, { name = "colorama" }, - { name = "distro", marker = "platform_system == "FreeBSD" or sys_platform == "linux"" }, + { name = "distro", marker = "platform_system == 'FreeBSD' or sys_platform == 'linux'" }, { name = "fasteners" }, { name = "jinja2" }, { name = "patch-ng" }, @@ -124,40 +120,40 @@ sdist = { url = "https://files.pythonhosted.org/packages/97/ee/79accc76c89254269 [[package]] name = "coverage" -version = "7.6.9" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5b/d2/c25011f4d036cf7e8acbbee07a8e09e9018390aee25ba085596c4b83d510/coverage-7.6.9.tar.gz", hash = "sha256:4a8d8977b0c6ef5aeadcb644da9e69ae0dcfe66ec7f368c89c72e058bd71164d", size = 801710 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/60/52/b16af8989a2daf0f80a88522bd8e8eed90b5fcbdecf02a6888f3e80f6ba7/coverage-7.6.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:99e266ae0b5d15f1ca8d278a668df6f51cc4b854513daab5cae695ed7b721cf8", size = 207325 }, - { url = "https://files.pythonhosted.org/packages/0f/79/6b7826fca8846c1216a113227b9f114ac3e6eacf168b4adcad0cb974aaca/coverage-7.6.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9901d36492009a0a9b94b20e52ebfc8453bf49bb2b27bca2c9706f8b4f5a554a", size = 207563 }, - { url = "https://files.pythonhosted.org/packages/a7/07/0bc73da0ccaf45d0d64ef86d33b7d7fdeef84b4c44bf6b85fb12c215c5a6/coverage-7.6.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abd3e72dd5b97e3af4246cdada7738ef0e608168de952b837b8dd7e90341f015", size = 240580 }, - { url = "https://files.pythonhosted.org/packages/71/8a/9761f409910961647d892454687cedbaccb99aae828f49486734a82ede6e/coverage-7.6.9-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff74026a461eb0660366fb01c650c1d00f833a086b336bdad7ab00cc952072b3", size = 237613 }, - { url = "https://files.pythonhosted.org/packages/8b/10/ee7d696a17ac94f32f2dbda1e17e730bf798ae9931aec1fc01c1944cd4de/coverage-7.6.9-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65dad5a248823a4996724a88eb51d4b31587aa7aa428562dbe459c684e5787ae", size = 239684 }, - { url = "https://files.pythonhosted.org/packages/16/60/aa1066040d3c52fff051243c2d6ccda264da72dc6d199d047624d395b2b2/coverage-7.6.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:22be16571504c9ccea919fcedb459d5ab20d41172056206eb2994e2ff06118a4", size = 239112 }, - { url = "https://files.pythonhosted.org/packages/4e/e5/69f35344c6f932ba9028bf168d14a79fedb0dd4849b796d43c81ce75a3c9/coverage-7.6.9-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f957943bc718b87144ecaee70762bc2bc3f1a7a53c7b861103546d3a403f0a6", size = 237428 }, - { url = "https://files.pythonhosted.org/packages/32/20/adc895523c4a28f63441b8ac645abd74f9bdd499d2d175bef5b41fc7f92d/coverage-7.6.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ae1387db4aecb1f485fb70a6c0148c6cdaebb6038f1d40089b1fc84a5db556f", size = 239098 }, - { url = "https://files.pythonhosted.org/packages/a9/a6/e0e74230c9bb3549ec8ffc137cfd16ea5d56e993d6bffed2218bff6187e3/coverage-7.6.9-cp312-cp312-win32.whl", hash = "sha256:1a330812d9cc7ac2182586f6d41b4d0fadf9be9049f350e0efb275c8ee8eb692", size = 209940 }, - { url = "https://files.pythonhosted.org/packages/3e/18/cb5b88349d4aa2f41ec78d65f92ea32572b30b3f55bc2b70e87578b8f434/coverage-7.6.9-cp312-cp312-win_amd64.whl", hash = "sha256:b12c6b18269ca471eedd41c1b6a1065b2f7827508edb9a7ed5555e9a56dcfc97", size = 210726 }, - { url = "https://files.pythonhosted.org/packages/35/26/9abab6539d2191dbda2ce8c97b67d74cbfc966cc5b25abb880ffc7c459bc/coverage-7.6.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:899b8cd4781c400454f2f64f7776a5d87bbd7b3e7f7bda0cb18f857bb1334664", size = 207356 }, - { url = "https://files.pythonhosted.org/packages/44/da/d49f19402240c93453f606e660a6676a2a1fbbaa6870cc23207790aa9697/coverage-7.6.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:61f70dc68bd36810972e55bbbe83674ea073dd1dcc121040a08cdf3416c5349c", size = 207614 }, - { url = "https://files.pythonhosted.org/packages/da/e6/93bb9bf85497816082ec8da6124c25efa2052bd4c887dd3b317b91990c9e/coverage-7.6.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a289d23d4c46f1a82d5db4abeb40b9b5be91731ee19a379d15790e53031c014", size = 240129 }, - { url = "https://files.pythonhosted.org/packages/df/65/6a824b9406fe066835c1274a9949e06f084d3e605eb1a602727a27ec2fe3/coverage-7.6.9-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e216d8044a356fc0337c7a2a0536d6de07888d7bcda76febcb8adc50bdbbd00", size = 237276 }, - { url = "https://files.pythonhosted.org/packages/9f/79/6c7a800913a9dd23ac8c8da133ebb556771a5a3d4df36b46767b1baffd35/coverage-7.6.9-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c026eb44f744acaa2bda7493dad903aa5bf5fc4f2554293a798d5606710055d", size = 239267 }, - { url = "https://files.pythonhosted.org/packages/57/e7/834d530293fdc8a63ba8ff70033d5182022e569eceb9aec7fc716b678a39/coverage-7.6.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e77363e8425325384f9d49272c54045bbed2f478e9dd698dbc65dbc37860eb0a", size = 238887 }, - { url = "https://files.pythonhosted.org/packages/15/05/ec9d6080852984f7163c96984444e7cd98b338fd045b191064f943ee1c08/coverage-7.6.9-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:777abfab476cf83b5177b84d7486497e034eb9eaea0d746ce0c1268c71652077", size = 236970 }, - { url = "https://files.pythonhosted.org/packages/0a/d8/775937670b93156aec29f694ce37f56214ed7597e1a75b4083ee4c32121c/coverage-7.6.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:447af20e25fdbe16f26e84eb714ba21d98868705cb138252d28bc400381f6ffb", size = 238831 }, - { url = "https://files.pythonhosted.org/packages/f4/58/88551cb7fdd5ec98cb6044e8814e38583436b14040a5ece15349c44c8f7c/coverage-7.6.9-cp313-cp313-win32.whl", hash = "sha256:d872ec5aeb086cbea771c573600d47944eea2dcba8be5f3ee649bfe3cb8dc9ba", size = 210000 }, - { url = "https://files.pythonhosted.org/packages/b7/12/cfbf49b95120872785ff8d56ab1c7fe3970a65e35010c311d7dd35c5fd00/coverage-7.6.9-cp313-cp313-win_amd64.whl", hash = "sha256:fd1213c86e48dfdc5a0cc676551db467495a95a662d2396ecd58e719191446e1", size = 210753 }, - { url = "https://files.pythonhosted.org/packages/7c/68/c1cb31445599b04bde21cbbaa6d21b47c5823cdfef99eae470dfce49c35a/coverage-7.6.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ba9e7484d286cd5a43744e5f47b0b3fb457865baf07bafc6bee91896364e1419", size = 208091 }, - { url = "https://files.pythonhosted.org/packages/11/73/84b02c6b19c4a11eb2d5b5eabe926fb26c21c080e0852f5e5a4f01165f9e/coverage-7.6.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e5ea1cf0872ee455c03e5674b5bca5e3e68e159379c1af0903e89f5eba9ccc3a", size = 208369 }, - { url = "https://files.pythonhosted.org/packages/de/e0/ae5d878b72ff26df2e994a5c5b1c1f6a7507d976b23beecb1ed4c85411ef/coverage-7.6.9-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d10e07aa2b91835d6abec555ec8b2733347956991901eea6ffac295f83a30e4", size = 251089 }, - { url = "https://files.pythonhosted.org/packages/ab/9c/0aaac011aef95a93ef3cb2fba3fde30bc7e68a6635199ed469b1f5ea355a/coverage-7.6.9-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:13a9e2d3ee855db3dd6ea1ba5203316a1b1fd8eaeffc37c5b54987e61e4194ae", size = 246806 }, - { url = "https://files.pythonhosted.org/packages/f8/19/4d5d3ae66938a7dcb2f58cef3fa5386f838f469575b0bb568c8cc9e3a33d/coverage-7.6.9-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c38bf15a40ccf5619fa2fe8f26106c7e8e080d7760aeccb3722664c8656b030", size = 249164 }, - { url = "https://files.pythonhosted.org/packages/b3/0b/4ee8a7821f682af9ad440ae3c1e379da89a998883271f088102d7ca2473d/coverage-7.6.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d5275455b3e4627c8e7154feaf7ee0743c2e7af82f6e3b561967b1cca755a0be", size = 248642 }, - { url = "https://files.pythonhosted.org/packages/8a/12/36ff1d52be18a16b4700f561852e7afd8df56363a5edcfb04cf26a0e19e0/coverage-7.6.9-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8f8770dfc6e2c6a2d4569f411015c8d751c980d17a14b0530da2d7f27ffdd88e", size = 246516 }, - { url = "https://files.pythonhosted.org/packages/43/d0/8e258f6c3a527c1655602f4f576215e055ac704de2d101710a71a2affac2/coverage-7.6.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8d2dfa71665a29b153a9681edb1c8d9c1ea50dfc2375fb4dac99ea7e21a0bcd9", size = 247783 }, - { url = "https://files.pythonhosted.org/packages/a9/0d/1e4a48d289429d38aae3babdfcadbf35ca36bdcf3efc8f09b550a845bdb5/coverage-7.6.9-cp313-cp313t-win32.whl", hash = "sha256:5e6b86b5847a016d0fbd31ffe1001b63355ed309651851295315031ea7eb5a9b", size = 210646 }, - { url = "https://files.pythonhosted.org/packages/26/74/b0729f196f328ac55e42b1e22ec2f16d8bcafe4b8158a26ec9f1cdd1d93e/coverage-7.6.9-cp313-cp313t-win_amd64.whl", hash = "sha256:97ddc94d46088304772d21b060041c97fc16bdda13c6c7f9d8fcd8d5ae0d8611", size = 211815 }, +version = "7.6.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/84/ba/ac14d281f80aab516275012e8875991bb06203957aa1e19950139238d658/coverage-7.6.10.tar.gz", hash = "sha256:7fb105327c8f8f0682e29843e2ff96af9dcbe5bab8eeb4b398c6a33a16d80a23", size = 803868 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/77/19d09ea06f92fdf0487499283b1b7af06bc422ea94534c8fe3a4cd023641/coverage-7.6.10-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:27c6e64726b307782fa5cbe531e7647aee385a29b2107cd87ba7c0105a5d3853", size = 208281 }, + { url = "https://files.pythonhosted.org/packages/b6/67/5479b9f2f99fcfb49c0d5cf61912a5255ef80b6e80a3cddba39c38146cf4/coverage-7.6.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c56e097019e72c373bae32d946ecf9858fda841e48d82df7e81c63ac25554078", size = 208514 }, + { url = "https://files.pythonhosted.org/packages/15/d1/febf59030ce1c83b7331c3546d7317e5120c5966471727aa7ac157729c4b/coverage-7.6.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7827a5bc7bdb197b9e066cdf650b2887597ad124dd99777332776f7b7c7d0d0", size = 241537 }, + { url = "https://files.pythonhosted.org/packages/4b/7e/5ac4c90192130e7cf8b63153fe620c8bfd9068f89a6d9b5f26f1550f7a26/coverage-7.6.10-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:204a8238afe787323a8b47d8be4df89772d5c1e4651b9ffa808552bdf20e1d50", size = 238572 }, + { url = "https://files.pythonhosted.org/packages/dc/03/0334a79b26ecf59958f2fe9dd1f5ab3e2f88db876f5071933de39af09647/coverage-7.6.10-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e67926f51821b8e9deb6426ff3164870976fe414d033ad90ea75e7ed0c2e5022", size = 240639 }, + { url = "https://files.pythonhosted.org/packages/d7/45/8a707f23c202208d7b286d78ad6233f50dcf929319b664b6cc18a03c1aae/coverage-7.6.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e78b270eadb5702938c3dbe9367f878249b5ef9a2fcc5360ac7bff694310d17b", size = 240072 }, + { url = "https://files.pythonhosted.org/packages/66/02/603ce0ac2d02bc7b393279ef618940b4a0535b0868ee791140bda9ecfa40/coverage-7.6.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:714f942b9c15c3a7a5fe6876ce30af831c2ad4ce902410b7466b662358c852c0", size = 238386 }, + { url = "https://files.pythonhosted.org/packages/04/62/4e6887e9be060f5d18f1dd58c2838b2d9646faf353232dec4e2d4b1c8644/coverage-7.6.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:abb02e2f5a3187b2ac4cd46b8ced85a0858230b577ccb2c62c81482ca7d18852", size = 240054 }, + { url = "https://files.pythonhosted.org/packages/5c/74/83ae4151c170d8bd071924f212add22a0e62a7fe2b149edf016aeecad17c/coverage-7.6.10-cp312-cp312-win32.whl", hash = "sha256:55b201b97286cf61f5e76063f9e2a1d8d2972fc2fcfd2c1272530172fd28c359", size = 210904 }, + { url = "https://files.pythonhosted.org/packages/c3/54/de0893186a221478f5880283119fc40483bc460b27c4c71d1b8bba3474b9/coverage-7.6.10-cp312-cp312-win_amd64.whl", hash = "sha256:e4ae5ac5e0d1e4edfc9b4b57b4cbecd5bc266a6915c500f358817a8496739247", size = 211692 }, + { url = "https://files.pythonhosted.org/packages/25/6d/31883d78865529257bf847df5789e2ae80e99de8a460c3453dbfbe0db069/coverage-7.6.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05fca8ba6a87aabdd2d30d0b6c838b50510b56cdcfc604d40760dae7153b73d9", size = 208308 }, + { url = "https://files.pythonhosted.org/packages/70/22/3f2b129cc08de00c83b0ad6252e034320946abfc3e4235c009e57cfeee05/coverage-7.6.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9e80eba8801c386f72e0712a0453431259c45c3249f0009aff537a517b52942b", size = 208565 }, + { url = "https://files.pythonhosted.org/packages/97/0a/d89bc2d1cc61d3a8dfe9e9d75217b2be85f6c73ebf1b9e3c2f4e797f4531/coverage-7.6.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a372c89c939d57abe09e08c0578c1d212e7a678135d53aa16eec4430adc5e690", size = 241083 }, + { url = "https://files.pythonhosted.org/packages/4c/81/6d64b88a00c7a7aaed3a657b8eaa0931f37a6395fcef61e53ff742b49c97/coverage-7.6.10-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec22b5e7fe7a0fa8509181c4aac1db48f3dd4d3a566131b313d1efc102892c18", size = 238235 }, + { url = "https://files.pythonhosted.org/packages/9a/0b/7797d4193f5adb4b837207ed87fecf5fc38f7cc612b369a8e8e12d9fa114/coverage-7.6.10-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26bcf5c4df41cad1b19c84af71c22cbc9ea9a547fc973f1f2cc9a290002c8b3c", size = 240220 }, + { url = "https://files.pythonhosted.org/packages/65/4d/6f83ca1bddcf8e51bf8ff71572f39a1c73c34cf50e752a952c34f24d0a60/coverage-7.6.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e4630c26b6084c9b3cb53b15bd488f30ceb50b73c35c5ad7871b869cb7365fd", size = 239847 }, + { url = "https://files.pythonhosted.org/packages/30/9d/2470df6aa146aff4c65fee0f87f58d2164a67533c771c9cc12ffcdb865d5/coverage-7.6.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2396e8116db77789f819d2bc8a7e200232b7a282c66e0ae2d2cd84581a89757e", size = 237922 }, + { url = "https://files.pythonhosted.org/packages/08/dd/723fef5d901e6a89f2507094db66c091449c8ba03272861eaefa773ad95c/coverage-7.6.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:79109c70cc0882e4d2d002fe69a24aa504dec0cc17169b3c7f41a1d341a73694", size = 239783 }, + { url = "https://files.pythonhosted.org/packages/3d/f7/64d3298b2baf261cb35466000628706ce20a82d42faf9b771af447cd2b76/coverage-7.6.10-cp313-cp313-win32.whl", hash = "sha256:9e1747bab246d6ff2c4f28b4d186b205adced9f7bd9dc362051cc37c4a0c7bd6", size = 210965 }, + { url = "https://files.pythonhosted.org/packages/d5/58/ec43499a7fc681212fe7742fe90b2bc361cdb72e3181ace1604247a5b24d/coverage-7.6.10-cp313-cp313-win_amd64.whl", hash = "sha256:254f1a3b1eef5f7ed23ef265eaa89c65c8c5b6b257327c149db1ca9d4a35f25e", size = 211719 }, + { url = "https://files.pythonhosted.org/packages/ab/c9/f2857a135bcff4330c1e90e7d03446b036b2363d4ad37eb5e3a47bbac8a6/coverage-7.6.10-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2ccf240eb719789cedbb9fd1338055de2761088202a9a0b73032857e53f612fe", size = 209050 }, + { url = "https://files.pythonhosted.org/packages/aa/b3/f840e5bd777d8433caa9e4a1eb20503495709f697341ac1a8ee6a3c906ad/coverage-7.6.10-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0c807ca74d5a5e64427c8805de15b9ca140bba13572d6d74e262f46f50b13273", size = 209321 }, + { url = "https://files.pythonhosted.org/packages/85/7d/125a5362180fcc1c03d91850fc020f3831d5cda09319522bcfa6b2b70be7/coverage-7.6.10-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bcfa46d7709b5a7ffe089075799b902020b62e7ee56ebaed2f4bdac04c508d8", size = 252039 }, + { url = "https://files.pythonhosted.org/packages/a9/9c/4358bf3c74baf1f9bddd2baf3756b54c07f2cfd2535f0a47f1e7757e54b3/coverage-7.6.10-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e0de1e902669dccbf80b0415fb6b43d27edca2fbd48c74da378923b05316098", size = 247758 }, + { url = "https://files.pythonhosted.org/packages/cf/c7/de3eb6fc5263b26fab5cda3de7a0f80e317597a4bad4781859f72885f300/coverage-7.6.10-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7b444c42bbc533aaae6b5a2166fd1a797cdb5eb58ee51a92bee1eb94a1e1cb", size = 250119 }, + { url = "https://files.pythonhosted.org/packages/3e/e6/43de91f8ba2ec9140c6a4af1102141712949903dc732cf739167cfa7a3bc/coverage-7.6.10-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b330368cb99ef72fcd2dc3ed260adf67b31499584dc8a20225e85bfe6f6cfed0", size = 249597 }, + { url = "https://files.pythonhosted.org/packages/08/40/61158b5499aa2adf9e37bc6d0117e8f6788625b283d51e7e0c53cf340530/coverage-7.6.10-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9a7cfb50515f87f7ed30bc882f68812fd98bc2852957df69f3003d22a2aa0abf", size = 247473 }, + { url = "https://files.pythonhosted.org/packages/50/69/b3f2416725621e9f112e74e8470793d5b5995f146f596f133678a633b77e/coverage-7.6.10-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f93531882a5f68c28090f901b1d135de61b56331bba82028489bc51bdd818d2", size = 248737 }, + { url = "https://files.pythonhosted.org/packages/3c/6e/fe899fb937657db6df31cc3e61c6968cb56d36d7326361847440a430152e/coverage-7.6.10-cp313-cp313t-win32.whl", hash = "sha256:89d76815a26197c858f53c7f6a656686ec392b25991f9e409bcef020cd532312", size = 211611 }, + { url = "https://files.pythonhosted.org/packages/1c/55/52f5e66142a9d7bc93a15192eba7a78513d2abf6b3558d77b4ca32f5f424/coverage-7.6.10-cp313-cp313t-win_amd64.whl", hash = "sha256:54a5f0f43950a36312155dae55c505a76cd7f2b12d26abeebbe7a0b36dbc868d", size = 212781 }, ] [[package]] @@ -285,11 +281,11 @@ wheels = [ [[package]] name = "importlib-resources" -version = "6.4.5" +version = "6.5.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/98/be/f3e8c6081b684f176b761e6a2fef02a0be939740ed6f54109a2951d806f3/importlib_resources-6.4.5.tar.gz", hash = "sha256:980862a1d16c9e147a59603677fa2aa5fd82b87f223b6cb870695bcfce830065", size = 43372 } +sdist = { url = "https://files.pythonhosted.org/packages/cf/8c/f834fbf984f691b4f7ff60f50b514cc3de5cc08abfc3295564dd89c5e2e7/importlib_resources-6.5.2.tar.gz", hash = "sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c", size = 44693 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/6a/4604f9ae2fa62ef47b9de2fa5ad599589d28c9fd1d335f32759813dfa91e/importlib_resources-6.4.5-py3-none-any.whl", hash = "sha256:ac29d5f956f01d5e4bb63102a5a19957f1b9175e45649977264a1416783bb717", size = 36115 }, + { url = "https://files.pythonhosted.org/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec", size = 37461 }, ] [[package]] @@ -379,25 +375,27 @@ wheels = [ [[package]] name = "mypy" -version = "1.14.0" +version = "1.14.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mypy-extensions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8c/7b/08046ef9330735f536a09a2e31b00f42bccdb2795dcd979636ba43bb2d63/mypy-1.14.0.tar.gz", hash = "sha256:822dbd184d4a9804df5a7d5335a68cf7662930e70b8c1bc976645d1509f9a9d6", size = 3215684 } +sdist = { url = "https://files.pythonhosted.org/packages/b9/eb/2c92d8ea1e684440f54fa49ac5d9a5f19967b7b472a281f419e69a8d228e/mypy-1.14.1.tar.gz", hash = "sha256:7ec88144fe9b510e8475ec2f5f251992690fcf89ccb4500b214b4226abcd32d6", size = 3216051 } wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/d8/0e72175ee0253217f5c44524f5e95251c02e95ba9749fb87b0e2074d203a/mypy-1.14.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d5326ab70a6db8e856d59ad4cb72741124950cbbf32e7b70e30166ba7bbf61dd", size = 11269011 }, - { url = "https://files.pythonhosted.org/packages/e9/6d/4ea13839dabe5db588dc6a1b766da16f420d33cf118a7b7172cdf6c7fcb2/mypy-1.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bf4ec4980bec1e0e24e5075f449d014011527ae0055884c7e3abc6a99cd2c7f1", size = 10253076 }, - { url = "https://files.pythonhosted.org/packages/3e/38/7db2c5d0f4d290e998f7a52b2e2616c7bbad96b8e04278ab09d11978a29e/mypy-1.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:390dfb898239c25289495500f12fa73aa7f24a4c6d90ccdc165762462b998d63", size = 12862786 }, - { url = "https://files.pythonhosted.org/packages/bf/4b/62d59c801b34141040989949c2b5c157d0408b45357335d3ec5b2845b0f6/mypy-1.14.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7e026d55ddcd76e29e87865c08cbe2d0104e2b3153a523c529de584759379d3d", size = 12971568 }, - { url = "https://files.pythonhosted.org/packages/f1/9c/e0f281b32d70c87b9e4d2939e302b1ff77ada4d7b0f2fb32890c144bc1d6/mypy-1.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:585ed36031d0b3ee362e5107ef449a8b5dfd4e9c90ccbe36414ee405ee6b32ba", size = 9879477 }, - { url = "https://files.pythonhosted.org/packages/13/33/8380efd0ebdfdfac7fc0bf065f03a049800ca1e6c296ec1afc634340d992/mypy-1.14.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9f6f4c0b27401d14c483c622bc5105eff3911634d576bbdf6695b9a7c1ba741", size = 11251509 }, - { url = "https://files.pythonhosted.org/packages/15/6d/4e1c21c60fee11af7d8e4f2902a29886d1387d6a836be16229eb3982a963/mypy-1.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56b2280cedcb312c7a79f5001ae5325582d0d339bce684e4a529069d0e7ca1e7", size = 10244282 }, - { url = "https://files.pythonhosted.org/packages/8b/cf/7a8ae5c0161edae15d25c2c67c68ce8b150cbdc45aefc13a8be271ee80b2/mypy-1.14.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:342de51c48bab326bfc77ce056ba08c076d82ce4f5a86621f972ed39970f94d8", size = 12867676 }, - { url = "https://files.pythonhosted.org/packages/9c/d0/71f7bbdcc7cfd0f2892db5b13b1e8857673f2cc9e0c30e3e4340523dc186/mypy-1.14.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:00df23b42e533e02a6f0055e54de9a6ed491cd8b7ea738647364fd3a39ea7efc", size = 12964189 }, - { url = "https://files.pythonhosted.org/packages/a7/40/fb4ad65d6d5f8c51396ecf6305ec0269b66013a5bf02d0e9528053640b4a/mypy-1.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:e8c8387e5d9dff80e7daf961df357c80e694e942d9755f3ad77d69b0957b8e3f", size = 9888247 }, - { url = "https://files.pythonhosted.org/packages/39/32/0214608af400cdf8f5102144bb8af10d880675c65ed0b58f7e0e77175d50/mypy-1.14.0-py3-none-any.whl", hash = "sha256:2238d7f93fc4027ed1efc944507683df3ba406445a2b6c96e79666a045aadfab", size = 2752803 }, + { url = "https://files.pythonhosted.org/packages/43/1b/b38c079609bb4627905b74fc6a49849835acf68547ac33d8ceb707de5f52/mypy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:30ff5ef8519bbc2e18b3b54521ec319513a26f1bba19a7582e7b1f58a6e69f14", size = 11266668 }, + { url = "https://files.pythonhosted.org/packages/6b/75/2ed0d2964c1ffc9971c729f7a544e9cd34b2cdabbe2d11afd148d7838aa2/mypy-1.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb9f255c18052343c70234907e2e532bc7e55a62565d64536dbc7706a20b78b9", size = 10254060 }, + { url = "https://files.pythonhosted.org/packages/a1/5f/7b8051552d4da3c51bbe8fcafffd76a6823779101a2b198d80886cd8f08e/mypy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b4e3413e0bddea671012b063e27591b953d653209e7a4fa5e48759cda77ca11", size = 11933167 }, + { url = "https://files.pythonhosted.org/packages/04/90/f53971d3ac39d8b68bbaab9a4c6c58c8caa4d5fd3d587d16f5927eeeabe1/mypy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:553c293b1fbdebb6c3c4030589dab9fafb6dfa768995a453d8a5d3b23784af2e", size = 12864341 }, + { url = "https://files.pythonhosted.org/packages/03/d2/8bc0aeaaf2e88c977db41583559319f1821c069e943ada2701e86d0430b7/mypy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fad79bfe3b65fe6a1efaed97b445c3d37f7be9fdc348bdb2d7cac75579607c89", size = 12972991 }, + { url = "https://files.pythonhosted.org/packages/6f/17/07815114b903b49b0f2cf7499f1c130e5aa459411596668267535fe9243c/mypy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:8fa2220e54d2946e94ab6dbb3ba0a992795bd68b16dc852db33028df2b00191b", size = 9879016 }, + { url = "https://files.pythonhosted.org/packages/9e/15/bb6a686901f59222275ab228453de741185f9d54fecbaacec041679496c6/mypy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:92c3ed5afb06c3a8e188cb5da4984cab9ec9a77ba956ee419c68a388b4595255", size = 11252097 }, + { url = "https://files.pythonhosted.org/packages/f8/b3/8b0f74dfd072c802b7fa368829defdf3ee1566ba74c32a2cb2403f68024c/mypy-1.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dbec574648b3e25f43d23577309b16534431db4ddc09fda50841f1e34e64ed34", size = 10239728 }, + { url = "https://files.pythonhosted.org/packages/c5/9b/4fd95ab20c52bb5b8c03cc49169be5905d931de17edfe4d9d2986800b52e/mypy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c6d94b16d62eb3e947281aa7347d78236688e21081f11de976376cf010eb31a", size = 11924965 }, + { url = "https://files.pythonhosted.org/packages/56/9d/4a236b9c57f5d8f08ed346914b3f091a62dd7e19336b2b2a0d85485f82ff/mypy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d4b19b03fdf54f3c5b2fa474c56b4c13c9dbfb9a2db4370ede7ec11a2c5927d9", size = 12867660 }, + { url = "https://files.pythonhosted.org/packages/40/88/a61a5497e2f68d9027de2bb139c7bb9abaeb1be1584649fa9d807f80a338/mypy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0c911fde686394753fff899c409fd4e16e9b294c24bfd5e1ea4675deae1ac6fd", size = 12969198 }, + { url = "https://files.pythonhosted.org/packages/54/da/3d6fc5d92d324701b0c23fb413c853892bfe0e1dbe06c9138037d459756b/mypy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:8b21525cb51671219f5307be85f7e646a153e5acc656e5cebf64bfa076c50107", size = 9885276 }, + { url = "https://files.pythonhosted.org/packages/a0/b5/32dd67b69a16d088e533962e5044e51004176a9952419de0370cdaead0f8/mypy-1.14.1-py3-none-any.whl", hash = "sha256:b66a60cc4073aeb8ae00057f9c1f64d49e90f918fbcef9a977eb121da8b8f1d1", size = 2752905 }, ] [[package]] @@ -563,7 +561,7 @@ name = "pytest" version = "8.3.4" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "sys_platform == "win32"" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "iniconfig" }, { name = "packaging" }, { name = "pluggy" }, @@ -641,36 +639,36 @@ wheels = [ [[package]] name = "ruff" -version = "0.8.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/34/37/9c02181ef38d55b77d97c68b78e705fd14c0de0e5d085202bb2b52ce5be9/ruff-0.8.4.tar.gz", hash = "sha256:0d5f89f254836799af1615798caa5f80b7f935d7a670fad66c5007928e57ace8", size = 3402103 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/05/67/f480bf2f2723b2e49af38ed2be75ccdb2798fca7d56279b585c8f553aaab/ruff-0.8.4-py3-none-linux_armv6l.whl", hash = "sha256:58072f0c06080276804c6a4e21a9045a706584a958e644353603d36ca1eb8a60", size = 10546415 }, - { url = "https://files.pythonhosted.org/packages/eb/7a/5aba20312c73f1ce61814e520d1920edf68ca3b9c507bd84d8546a8ecaa8/ruff-0.8.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ffb60904651c00a1e0b8df594591770018a0f04587f7deeb3838344fe3adabac", size = 10346113 }, - { url = "https://files.pythonhosted.org/packages/76/f4/c41de22b3728486f0aa95383a44c42657b2db4062f3234ca36fc8cf52d8b/ruff-0.8.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6ddf5d654ac0d44389f6bf05cee4caeefc3132a64b58ea46738111d687352296", size = 9943564 }, - { url = "https://files.pythonhosted.org/packages/0e/f0/afa0d2191af495ac82d4cbbfd7a94e3df6f62a04ca412033e073b871fc6d/ruff-0.8.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e248b1f0fa2749edd3350a2a342b67b43a2627434c059a063418e3d375cfe643", size = 10805522 }, - { url = "https://files.pythonhosted.org/packages/12/57/5d1e9a0fd0c228e663894e8e3a8e7063e5ee90f8e8e60cf2085f362bfa1a/ruff-0.8.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf197b98ed86e417412ee3b6c893f44c8864f816451441483253d5ff22c0e81e", size = 10306763 }, - { url = "https://files.pythonhosted.org/packages/04/df/f069fdb02e408be8aac6853583572a2873f87f866fe8515de65873caf6b8/ruff-0.8.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c41319b85faa3aadd4d30cb1cffdd9ac6b89704ff79f7664b853785b48eccdf3", size = 11359574 }, - { url = "https://files.pythonhosted.org/packages/d3/04/37c27494cd02e4a8315680debfc6dfabcb97e597c07cce0044db1f9dfbe2/ruff-0.8.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:9f8402b7c4f96463f135e936d9ab77b65711fcd5d72e5d67597b543bbb43cf3f", size = 12094851 }, - { url = "https://files.pythonhosted.org/packages/81/b1/c5d7fb68506cab9832d208d03ea4668da9a9887a4a392f4f328b1bf734ad/ruff-0.8.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4e56b3baa9c23d324ead112a4fdf20db9a3f8f29eeabff1355114dd96014604", size = 11655539 }, - { url = "https://files.pythonhosted.org/packages/ef/38/8f8f2c8898dc8a7a49bc340cf6f00226917f0f5cb489e37075bcb2ce3671/ruff-0.8.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:736272574e97157f7edbbb43b1d046125fce9e7d8d583d5d65d0c9bf2c15addf", size = 12912805 }, - { url = "https://files.pythonhosted.org/packages/06/dd/fa6660c279f4eb320788876d0cff4ea18d9af7d9ed7216d7bd66877468d0/ruff-0.8.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5fe710ab6061592521f902fca7ebcb9fabd27bc7c57c764298b1c1f15fff720", size = 11205976 }, - { url = "https://files.pythonhosted.org/packages/a8/d7/de94cc89833b5de455750686c17c9e10f4e1ab7ccdc5521b8fe911d1477e/ruff-0.8.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:13e9ec6d6b55f6da412d59953d65d66e760d583dd3c1c72bf1f26435b5bfdbae", size = 10792039 }, - { url = "https://files.pythonhosted.org/packages/6d/15/3e4906559248bdbb74854af684314608297a05b996062c9d72e0ef7c7097/ruff-0.8.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:97d9aefef725348ad77d6db98b726cfdb075a40b936c7984088804dfd38268a7", size = 10400088 }, - { url = "https://files.pythonhosted.org/packages/a2/21/9ed4c0e8133cb4a87a18d470f534ad1a8a66d7bec493bcb8bda2d1a5d5be/ruff-0.8.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:ab78e33325a6f5374e04c2ab924a3367d69a0da36f8c9cb6b894a62017506111", size = 10900814 }, - { url = "https://files.pythonhosted.org/packages/0d/5d/122a65a18955bd9da2616b69bc839351f8baf23b2805b543aa2f0aed72b5/ruff-0.8.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:8ef06f66f4a05c3ddbc9121a8b0cecccd92c5bf3dd43b5472ffe40b8ca10f0f8", size = 11268828 }, - { url = "https://files.pythonhosted.org/packages/43/a9/1676ee9106995381e3d34bccac5bb28df70194167337ed4854c20f27c7ba/ruff-0.8.4-py3-none-win32.whl", hash = "sha256:552fb6d861320958ca5e15f28b20a3d071aa83b93caee33a87b471f99a6c0835", size = 8805621 }, - { url = "https://files.pythonhosted.org/packages/10/98/ed6b56a30ee76771c193ff7ceeaf1d2acc98d33a1a27b8479cbdb5c17a23/ruff-0.8.4-py3-none-win_amd64.whl", hash = "sha256:f21a1143776f8656d7f364bd264a9d60f01b7f52243fbe90e7670c0dfe0cf65d", size = 9660086 }, - { url = "https://files.pythonhosted.org/packages/13/9f/026e18ca7d7766783d779dae5e9c656746c6ede36ef73c6d934aaf4a6dec/ruff-0.8.4-py3-none-win_arm64.whl", hash = "sha256:9183dd615d8df50defa8b1d9a074053891ba39025cf5ae88e8bcb52edcc4bf08", size = 9074500 }, +version = "0.8.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/00/089db7890ea3be5709e3ece6e46408d6f1e876026ec3fd081ee585fef209/ruff-0.8.6.tar.gz", hash = "sha256:dcad24b81b62650b0eb8814f576fc65cfee8674772a6e24c9b747911801eeaa5", size = 3473116 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/28/aa07903694637c2fa394a9f4fe93cf861ad8b09f1282fa650ef07ff9fe97/ruff-0.8.6-py3-none-linux_armv6l.whl", hash = "sha256:defed167955d42c68b407e8f2e6f56ba52520e790aba4ca707a9c88619e580e3", size = 10628735 }, + { url = "https://files.pythonhosted.org/packages/2b/43/827bb1448f1fcb0fb42e9c6edf8fb067ca8244923bf0ddf12b7bf949065c/ruff-0.8.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:54799ca3d67ae5e0b7a7ac234baa657a9c1784b48ec954a094da7c206e0365b1", size = 10386758 }, + { url = "https://files.pythonhosted.org/packages/df/93/fc852a81c3cd315b14676db3b8327d2bb2d7508649ad60bfdb966d60738d/ruff-0.8.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e88b8f6d901477c41559ba540beeb5a671e14cd29ebd5683903572f4b40a9807", size = 10007808 }, + { url = "https://files.pythonhosted.org/packages/94/e9/e0ed4af1794335fb280c4fac180f2bf40f6a3b859cae93a5a3ada27325ae/ruff-0.8.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0509e8da430228236a18a677fcdb0c1f102dd26d5520f71f79b094963322ed25", size = 10861031 }, + { url = "https://files.pythonhosted.org/packages/82/68/da0db02f5ecb2ce912c2bef2aa9fcb8915c31e9bc363969cfaaddbc4c1c2/ruff-0.8.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:91a7ddb221779871cf226100e677b5ea38c2d54e9e2c8ed847450ebbdf99b32d", size = 10388246 }, + { url = "https://files.pythonhosted.org/packages/ac/1d/b85383db181639019b50eb277c2ee48f9f5168f4f7c287376f2b6e2a6dc2/ruff-0.8.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:248b1fb3f739d01d528cc50b35ee9c4812aa58cc5935998e776bf8ed5b251e75", size = 11424693 }, + { url = "https://files.pythonhosted.org/packages/ac/b7/30bc78a37648d31bfc7ba7105b108cb9091cd925f249aa533038ebc5a96f/ruff-0.8.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:bc3c083c50390cf69e7e1b5a5a7303898966be973664ec0c4a4acea82c1d4315", size = 12141921 }, + { url = "https://files.pythonhosted.org/packages/60/b3/ee0a14cf6a1fbd6965b601c88d5625d250b97caf0534181e151504498f86/ruff-0.8.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52d587092ab8df308635762386f45f4638badb0866355b2b86760f6d3c076188", size = 11692419 }, + { url = "https://files.pythonhosted.org/packages/ef/d6/c597062b2931ba3e3861e80bd2b147ca12b3370afc3889af46f29209037f/ruff-0.8.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:61323159cf21bc3897674e5adb27cd9e7700bab6b84de40d7be28c3d46dc67cf", size = 12981648 }, + { url = "https://files.pythonhosted.org/packages/68/84/21f578c2a4144917985f1f4011171aeff94ab18dfa5303ac632da2f9af36/ruff-0.8.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ae4478b1471fc0c44ed52a6fb787e641a2ac58b1c1f91763bafbc2faddc5117", size = 11251801 }, + { url = "https://files.pythonhosted.org/packages/6c/aa/1ac02537c8edeb13e0955b5db86b5c050a1dcba54f6d49ab567decaa59c1/ruff-0.8.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0c000a471d519b3e6cfc9c6680025d923b4ca140ce3e4612d1a2ef58e11f11fe", size = 10849857 }, + { url = "https://files.pythonhosted.org/packages/eb/00/020cb222252d833956cb3b07e0e40c9d4b984fbb2dc3923075c8f944497d/ruff-0.8.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:9257aa841e9e8d9b727423086f0fa9a86b6b420fbf4bf9e1465d1250ce8e4d8d", size = 10470852 }, + { url = "https://files.pythonhosted.org/packages/00/56/e6d6578202a0141cd52299fe5acb38b2d873565f4670c7a5373b637cf58d/ruff-0.8.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:45a56f61b24682f6f6709636949ae8cc82ae229d8d773b4c76c09ec83964a95a", size = 10972997 }, + { url = "https://files.pythonhosted.org/packages/be/31/dd0db1f4796bda30dea7592f106f3a67a8f00bcd3a50df889fbac58e2786/ruff-0.8.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:496dd38a53aa173481a7d8866bcd6451bd934d06976a2505028a50583e001b76", size = 11317760 }, + { url = "https://files.pythonhosted.org/packages/d4/70/cfcb693dc294e034c6fed837fa2ec98b27cc97a26db5d049345364f504bf/ruff-0.8.6-py3-none-win32.whl", hash = "sha256:e169ea1b9eae61c99b257dc83b9ee6c76f89042752cb2d83486a7d6e48e8f764", size = 8799729 }, + { url = "https://files.pythonhosted.org/packages/60/22/ae6bcaa0edc83af42751bd193138bfb7598b2990939d3e40494d6c00698c/ruff-0.8.6-py3-none-win_amd64.whl", hash = "sha256:f1d70bef3d16fdc897ee290d7d20da3cbe4e26349f62e8a0274e7a3f4ce7a905", size = 9673857 }, + { url = "https://files.pythonhosted.org/packages/91/f8/3765e053acd07baa055c96b2065c7fab91f911b3c076dfea71006666f5b0/ruff-0.8.6-py3-none-win_arm64.whl", hash = "sha256:7d7fc2377a04b6e04ffe588caad613d0c460eb2ecba4c0ccbbfe2bc973cbc162", size = 9149556 }, ] [[package]] name = "setuptools" -version = "75.6.0" +version = "75.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/54/292f26c208734e9a7f067aea4a7e282c080750c4546559b58e2e45413ca0/setuptools-75.6.0.tar.gz", hash = "sha256:8199222558df7c86216af4f84c30e9b34a61d8ba19366cc914424cdbd28252f6", size = 1337429 } +sdist = { url = "https://files.pythonhosted.org/packages/ac/57/e6f0bde5a2c333a32fbcce201f906c1fd0b3a7144138712a5e9d9598c5ec/setuptools-75.7.0.tar.gz", hash = "sha256:886ff7b16cd342f1d1defc16fc98c9ce3fde69e087a4e1983d7ab634e5f41f4f", size = 1338616 } wheels = [ - { url = "https://files.pythonhosted.org/packages/55/21/47d163f615df1d30c094f6c8bbb353619274edccf0327b185cc2493c2c33/setuptools-75.6.0-py3-none-any.whl", hash = "sha256:ce74b49e8f7110f9bf04883b730f4765b774ef3ef28f722cce7c273d253aaf7d", size = 1224032 }, + { url = "https://files.pythonhosted.org/packages/4e/6e/abdfaaf5c294c553e7a81cf5d801fbb4f53f5c5b6646de651f92a2667547/setuptools-75.7.0-py3-none-any.whl", hash = "sha256:84fb203f278ebcf5cd08f97d3fb96d3fbed4b629d500b29ad60d11e00769b183", size = 1224467 }, ] [[package]] @@ -696,11 +694,11 @@ wheels = [ [[package]] name = "types-pyyaml" -version = "6.0.12.20241221" +version = "6.0.12.20241230" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f4/60/ba3f23024bdd406e65c359b9dbd9757f058986bd57d94f6639015f9a9fae/types_pyyaml-6.0.12.20241221.tar.gz", hash = "sha256:4f149aa893ff6a46889a30af4c794b23833014c469cc57cbc3ad77498a58996f", size = 17034 } +sdist = { url = "https://files.pythonhosted.org/packages/9a/f9/4d566925bcf9396136c0a2e5dc7e230ff08d86fa011a69888dd184469d80/types_pyyaml-6.0.12.20241230.tar.gz", hash = "sha256:7f07622dbd34bb9c8b264fe860a17e0efcad00d50b5f27e93984909d9363498c", size = 17078 } wheels = [ - { url = "https://files.pythonhosted.org/packages/4b/04/1cc4fffeb4ace85c205e84cd48eb12cb37ec6ffb68245b7eef8f2086d490/types_PyYAML-6.0.12.20241221-py3-none-any.whl", hash = "sha256:0657a4ff8411a030a2116a196e8e008ea679696b5b1a8e1a6aa8ebb737b34688", size = 20023 }, + { url = "https://files.pythonhosted.org/packages/e8/c1/48474fbead512b70ccdb4f81ba5eb4a58f69d100ba19f17c92c0c4f50ae6/types_PyYAML-6.0.12.20241230-py3-none-any.whl", hash = "sha256:fa4d32565219b68e6dee5f67534c722e53c00d1cfc09c435ef04d7353e1e96e6", size = 20029 }, ] [[package]]