diff --git a/Pipfile b/Pipfile index ab7c9814f3..01a392d730 100644 --- a/Pipfile +++ b/Pipfile @@ -43,3 +43,4 @@ test = "pytest -vvs" [pipenv] allow_prereleases = true +use_pylock = true diff --git a/Pipfile.lock b/Pipfile.lock index 8b099f589e..22172d80b5 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -44,6 +44,7 @@ "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11" ], "markers": "sys_platform == 'win32'", + "sys_platform": "== 'win32'", "version": "==1.4.1" }, "attrs": { @@ -363,6 +364,7 @@ "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" ], "markers": "sys_platform == 'win32'", + "sys_platform": "== 'win32'", "version": "==0.4.6" }, "coverage": { @@ -705,7 +707,8 @@ "version": "==25.7.0" }, "legacy-cgi": { - "markers": "python_version >= '3.13'" + "markers": "python_version >= '3.13'", + "version": "*" }, "linkify-it-py": { "hashes": [ @@ -1411,6 +1414,7 @@ "sha256:c56d67fd6e87c2ee598b76abdd4e96cfad1f24cacdea5078d382b1f9d7b5ed2e" ], "markers": "sys_platform == 'win32'", + "sys_platform": "== 'win32'", "version": "==3.0.2" }, "zipp": { diff --git a/docs/index.md b/docs/index.md index 955e3921c5..b029f9ca07 100644 --- a/docs/index.md +++ b/docs/index.md @@ -104,6 +104,7 @@ credentials shell docker scripts +pylock advanced diagnose changelog diff --git a/docs/pylock.md b/docs/pylock.md new file mode 100644 index 0000000000..83b1a45581 --- /dev/null +++ b/docs/pylock.md @@ -0,0 +1,169 @@ +# PEP 751 pylock.toml Support + +Pipenv supports [PEP 751](https://peps.python.org/pep-0751/) pylock.toml files, which provide a standardized format for recording Python dependencies to enable installation reproducibility. + +## What is pylock.toml? + +The pylock.toml file is a standardized lock file format introduced in PEP 751. It is designed to be: + +- Human-readable and machine-generated +- Secure by default (includes file hashes) +- Able to support both single-use and multi-use lock files +- Compatible across different Python packaging tools + +## Using pylock.toml with Pipenv + +Pipenv can automatically detect and use pylock.toml files in your project. When both a Pipfile.lock and a pylock.toml file exist, Pipenv will prioritize the pylock.toml file. + +### Reading pylock.toml Files + +When you run commands like `pipenv install` or `pipenv sync`, Pipenv will check for a pylock.toml file in your project directory. If found, it will use the dependencies specified in the pylock.toml file instead of Pipfile.lock. + +Pipenv looks for pylock.toml files in the following order: +1. A file named `pylock.toml` in the project directory +2. A file matching the pattern `pylock.*.toml` in the project directory + +### Example pylock.toml File + +Here's a simplified example of a pylock.toml file: + +```toml +lock-version = '1.0' +environments = ["sys_platform == 'win32'", "sys_platform == 'linux'", "sys_platform == 'darwin'"] +requires-python = '>=3.8' +extras = [] +dependency-groups = ['dev'] +default-groups = ['default'] +created-by = 'pipenv' + +[[packages]] +name = 'requests' +version = '2.28.1' +requires-python = '>=3.7' +index = 'https://pypi.org/simple/' + +[[packages.wheels]] +name = 'requests-2.28.1-py3-none-any.whl' +upload-time = '2022-07-13T14:00:00Z' +url = 'https://files.pythonhosted.org/packages/ca/91/6d9b8ccacd0412c08820f72cebaa4f0c61441f4AE7b7338a82051330d70/requests-2.28.1-py3-none-any.whl' +size = 61805 +hashes = {sha256 = 'b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7'} + +[[packages]] +name = 'pytest' +version = '7.0.0' +marker = "'dev' in dependency_groups" +index = 'https://pypi.org/simple/' +``` + +## Benefits of Using pylock.toml + +- **Standardization**: pylock.toml is a standardized format that can be used by multiple Python packaging tools. +- **Security**: pylock.toml includes file hashes by default, making it more secure against supply chain attacks. +- **Flexibility**: pylock.toml supports extras and dependency groups for multi-use lock files. +- **Interoperability**: pylock.toml can be used by different tools, reducing vendor lock-in. +- **Auditability**: Packages include their index URL for SBOM generation. + +## Writing pylock.toml Files + +Pipenv can generate pylock.toml files alongside Pipfile.lock files. To enable this feature, add the following to your Pipfile: + +```toml +[pipenv] +use_pylock = true +``` + +With this setting, whenever Pipenv updates the Pipfile.lock file (e.g., when running `pipenv lock`), it will also generate a pylock.toml file in the same directory. + +You can also specify a custom name for the pylock.toml file: + +```toml +[pipenv] +use_pylock = true +pylock_name = "dev" # This will generate pylock.dev.toml +``` + +## CLI Commands + +Pipenv provides a `pylock` command for managing pylock.toml files: + +### Generate pylock.toml from Pipfile.lock + +```bash +pipenv pylock --generate +``` + +### Generate pylock.toml from pyproject.toml + +Create a pylock.toml skeleton from your pyproject.toml dependencies (PEP 621/735): + +```bash +pipenv pylock --from-pyproject +``` + +**Note:** This creates a skeleton file with declared dependencies. Package versions and hashes need to be resolved by running `pipenv lock`. + +### Validate pylock.toml + +```bash +pipenv pylock --validate +``` + +### Custom Output Path + +```bash +pipenv pylock --generate --output /path/to/pylock.toml +``` + +### Custom Dependency Groups + +Specify which dependency groups should be used for develop packages: + +```bash +pipenv pylock --generate --dev-groups "dev,test,docs" +``` + +## pyproject.toml Support + +Pipenv can read dependencies from `pyproject.toml` files following PEP 621 and PEP 735: + +- `[project.dependencies]` - Main project dependencies +- `[project.optional-dependencies]` - Optional dependencies (extras) +- `[dependency-groups]` - Dependency groups (PEP 735) + +This allows you to use `pyproject.toml` as your primary dependency specification while generating standardized pylock.toml files. + +## Marker Evaluation + +Pipenv supports PEP 751 marker syntax for extras and dependency groups: + +- `'name' in extras` - Include package when extra is enabled +- `'name' in dependency_groups` - Include package when dependency group is enabled + +Example: + +```toml +[[packages]] +name = 'pytest' +version = '7.0.0' +marker = "'dev' in dependency_groups" +``` + +## Features + +### Implemented + +- ✅ Core pylock.toml format (read/write) +- ✅ Bidirectional conversion with Pipfile.lock +- ✅ Package index tracking (`packages.index`) +- ✅ Extras and dependency groups +- ✅ Marker evaluation for filtering packages +- ✅ CLI commands for generation and validation +- ✅ pyproject.toml dependency reading + +### Future Enhancements + +- VCS package support (`packages.vcs`) +- Local directory support (`packages.directory`) +- Direct archive support (`packages.archive`) +- Attestation identities support diff --git a/examples/Pipfile.with_pylock b/examples/Pipfile.with_pylock new file mode 100644 index 0000000000..62cec7a062 --- /dev/null +++ b/examples/Pipfile.with_pylock @@ -0,0 +1,21 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +requests = "*" + +[dev-packages] +pytest = "*" + +[requires] +python_version = "3.8" + +[pipenv] +# Enable pylock.toml generation +use_pylock = true + +# Optional: Specify a custom name for the pylock file +# This will generate pylock.dev.toml instead of pylock.toml +# pylock_name = "dev" diff --git a/examples/pylock.toml b/examples/pylock.toml new file mode 100644 index 0000000000..e60b108e9b --- /dev/null +++ b/examples/pylock.toml @@ -0,0 +1,83 @@ +# Example pylock.toml file for PEP 751 +# This demonstrates the pylock.toml format supported by Pipenv + +lock-version = '1.0' +environments = ["sys_platform == 'win32'", "sys_platform == 'linux'", "sys_platform == 'darwin'"] +requires-python = '>=3.8' +extras = ['crypto'] +dependency-groups = ['dev', 'test'] +default-groups = ['default'] +created-by = 'pipenv' + +# Main dependencies (always installed) +[[packages]] +name = 'requests' +version = '2.28.1' +requires-python = '>=3.7' +index = 'https://pypi.org/simple/' + +[[packages.wheels]] +name = 'requests-2.28.1-py3-none-any.whl' +upload-time = '2022-07-13T14:00:00Z' +url = 'https://files.pythonhosted.org/packages/ca/91/6d9b8ccacd0412c08820f72cebaa4f0c61441f4AE7b7338a82051330d70/requests-2.28.1-py3-none-any.whl' +size = 61805 +hashes = {sha256 = 'b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7'} + +[[packages]] +name = 'urllib3' +version = '1.26.12' +requires-python = '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4' +index = 'https://pypi.org/simple/' + +[[packages.wheels]] +name = 'urllib3-1.26.12-py2.py3-none-any.whl' +upload-time = '2022-09-08T14:30:00Z' +url = 'https://files.pythonhosted.org/packages/6f/de/5be2e3eed8426f871b170663333a0f627fc2924cc386cd41a2d2d3f1699/urllib3-1.26.12-py2.py3-none-any.whl' +size = 141862 +hashes = {sha256 = 'b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997'} + +[[packages]] +name = 'certifi' +version = '2022.9.24' +index = 'https://pypi.org/simple/' + +[[packages.wheels]] +name = 'certifi-2022.9.24-py3-none-any.whl' +upload-time = '2022-09-24T18:00:00Z' +url = 'https://files.pythonhosted.org/packages/1d/38/fa96a426e0c0e68aabc68e896584b83ad1eec779265a028e156ce509630/certifi-2022.9.24-py3-none-any.whl' +size = 161915 +hashes = {sha256 = '0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14'} + +# Development dependency (only when 'dev' group is enabled) +[[packages]] +name = 'pytest' +version = '7.2.0' +requires-python = '>=3.7' +marker = "'dev' in dependency_groups or 'test' in dependency_groups" +index = 'https://pypi.org/simple/' + +[[packages.wheels]] +name = 'pytest-7.2.0-py3-none-any.whl' +upload-time = '2022-11-12T10:00:00Z' +url = 'https://files.pythonhosted.org/packages/ab/cd/pytest-7.2.0-py3-none-any.whl' +size = 312500 +hashes = {sha256 = '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'} + +# Optional dependency for 'crypto' extra +[[packages]] +name = 'cryptography' +version = '40.0.0' +requires-python = '>=3.7' +marker = "'crypto' in extras" +index = 'https://pypi.org/simple/' + +[[packages.wheels]] +name = 'cryptography-40.0.0-py3-none-any.whl' +upload-time = '2023-03-20T08:00:00Z' +url = 'https://files.pythonhosted.org/packages/ef/gh/cryptography-40.0.0-py3-none-any.whl' +size = 654321 +hashes = {sha256 = 'abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890'} + +[tool.pipenv] +generated_from = "Pipfile.lock" +generation_date = "2025-04-25T04:20:00Z" diff --git a/news/6391.feature.rst b/news/6391.feature.rst new file mode 100644 index 0000000000..af3bc2bbd4 --- /dev/null +++ b/news/6391.feature.rst @@ -0,0 +1,5 @@ +Added support for PEP 751 pylock.toml files: + +- Reading: When both a Pipfile.lock and a pylock.toml file exist, Pipenv will prioritize the pylock.toml file. +- Writing: Add ``use_pylock = true`` to the ``[pipenv]`` section of your Pipfile to generate pylock.toml files alongside Pipfile.lock. +- Customization: Use ``pylock_name = "name"`` in the ``[pipenv]`` section to generate named pylock files (e.g., pylock.name.toml). diff --git a/pipenv/cli/command.py b/pipenv/cli/command.py index 7a0fc47723..246b948fba 100644 --- a/pipenv/cli/command.py +++ b/pipenv/cli/command.py @@ -794,6 +794,168 @@ def requirements( ) +@cli.command( + short_help="Manage PEP 751 pylock.toml files.", + context_settings=CONTEXT_SETTINGS, +) +@option( + "--generate", + is_flag=True, + default=False, + help="Generate pylock.toml from Pipfile.lock.", +) +@option( + "--from-pyproject", + is_flag=True, + default=False, + help="Generate pylock.toml skeleton from pyproject.toml.", +) +@option( + "--validate", + is_flag=True, + default=False, + help="Validate an existing pylock.toml file.", +) +@option( + "--output", + "-o", + default=None, + help="Output file path (default: pylock.toml in project directory).", +) +@option( + "--dev-groups", + default="dev", + help="Comma-separated list of dependency group names for dev packages.", +) +@common_options +@pass_state +def pylock( + state, + generate=False, + from_pyproject=False, + validate=False, + output=None, + dev_groups="dev", +): + """Manage PEP 751 pylock.toml files. + + Generate, validate, or convert pylock.toml files. + + Examples: + + pipenv pylock --generate + + pipenv pylock --from-pyproject + + pipenv pylock --validate + """ + from pipenv.utils.pylock import PylockFile, PylockFormatError, PylockVersionError + + project = state.project + + # Parse dev_groups + groups = [g.strip() for g in dev_groups.split(",") if g.strip()] + + if generate: + # Generate from Pipfile.lock + if not project.lockfile_exists: + err.print("[bold red]No Pipfile.lock found.[/bold red]") + sys.exit(1) + + try: + output_path = output or project.pylock_output_path + pylock_file = PylockFile.from_lockfile( + lockfile_path=project.lockfile_location, + pylock_path=output_path, + dev_groups=groups, + ) + pylock_file.write() + console.print( + f"[bold green]Generated pylock.toml at {output_path}[/bold green]" + ) + except Exception as e: + err.print(f"[bold red]Error generating pylock.toml: {e}[/bold red]") + sys.exit(1) + + elif from_pyproject: + # Generate skeleton from pyproject.toml + pyproject_path = Path(project.project_directory) / "pyproject.toml" + if not pyproject_path.exists(): + err.print("[bold red]No pyproject.toml found.[/bold red]") + sys.exit(1) + + try: + output_path = output or project.pylock_output_path + pylock_file = PylockFile.from_pyproject( + pyproject_path=pyproject_path, + pylock_path=output_path, + ) + pylock_file.write() + console.print( + f"[bold green]Generated pylock.toml skeleton at {output_path}[/bold green]" + ) + console.print( + "[yellow]Note: This is a skeleton file. Package versions and hashes " + "need to be resolved by running 'pipenv lock'.[/yellow]" + ) + except Exception as e: + err.print(f"[bold red]Error generating pylock.toml: {e}[/bold red]") + sys.exit(1) + + elif validate: + # Validate existing pylock.toml + pylock_path = project.pylock_location + if not pylock_path: + err.print("[bold red]No pylock.toml found.[/bold red]") + sys.exit(1) + + try: + pylock_file = PylockFile.from_path(pylock_path) + console.print( + f"[bold green]✓ Valid pylock.toml (version {pylock_file.lock_version})[/bold green]" + ) + console.print(f" Created by: {pylock_file.created_by}") + console.print(f" Packages: {len(pylock_file.packages)}") + if pylock_file.requires_python: + console.print(f" Requires Python: {pylock_file.requires_python}") + if pylock_file.extras: + console.print(f" Extras: {', '.join(pylock_file.extras)}") + if pylock_file.dependency_groups: + console.print( + f" Dependency Groups: {', '.join(pylock_file.dependency_groups)}" + ) + except PylockVersionError as e: + err.print(f"[bold red]Version error: {e}[/bold red]") + sys.exit(1) + except PylockFormatError as e: + err.print(f"[bold red]Format error: {e}[/bold red]") + sys.exit(1) + except Exception as e: + err.print(f"[bold red]Error validating pylock.toml: {e}[/bold red]") + sys.exit(1) + + else: + # Default: show status + pylock_path = project.pylock_location + if pylock_path: + try: + pylock_file = PylockFile.from_path(pylock_path) + console.print(f"[bold]pylock.toml[/bold]: {pylock_path}") + console.print(f" Version: {pylock_file.lock_version}") + console.print(f" Created by: {pylock_file.created_by}") + console.print(f" Packages: {len(pylock_file.packages)}") + except Exception as e: + err.print(f"[yellow]Found pylock.toml but could not parse: {e}[/yellow]") + else: + console.print("[dim]No pylock.toml found.[/dim]") + console.print( + "Use [bold]pipenv pylock --generate[/bold] to create one from Pipfile.lock" + ) + console.print( + "Use [bold]pipenv pylock --from-pyproject[/bold] to create from pyproject.toml" + ) + + if __name__ == "__main__": cli() diff --git a/pipenv/project.py b/pipenv/project.py index 887ab714c3..18432291f5 100644 --- a/pipenv/project.py +++ b/pipenv/project.py @@ -57,6 +57,7 @@ proper_case, ) from pipenv.utils.locking import atomic_open_for_write +from pipenv.utils.pylock import PylockFile, find_pylock_file from pipenv.utils.project import get_default_pyproject_backend from pipenv.utils.requirements import normalize_name from pipenv.utils.shell import ( @@ -793,6 +794,19 @@ def _pipfile(self): pf = ReqLibPipfile.load(self.pipfile_location) return pf + @property + def pylock_location(self): + """Returns the location of the pylock.toml file, if it exists.""" + pylock_path = find_pylock_file(self.project_directory) + if pylock_path: + return str(pylock_path) + return None + + @property + def pylock_exists(self): + """Returns True if a pylock.toml file exists.""" + return self.pylock_location is not None + @property def lockfile_location(self): return f"{self.pipfile_location}.lock" @@ -803,6 +817,15 @@ def lockfile_exists(self): @property def lockfile_content(self): + """Returns the content of the lockfile, checking for pylock.toml first.""" + if self.pylock_exists or self.use_pylock: + try: + if self.pylock_exists: + pylock = PylockFile.from_path(self.pylock_location) + lockfile_data = pylock.convert_to_pipenv_lockfile() + return lockfile_data + except Exception as e: + err.print(f"[bold yellow]Error loading pylock.toml: {e}[/bold yellow]") return self.load_lockfile() def get_editable_packages(self, category): @@ -986,8 +1009,22 @@ def write_toml(self, data, path=None): with open(path, "w", newline=newlines) as f: f.write(formatted_data) + @property + def use_pylock(self) -> bool: + """Returns True if pylock.toml should be generated.""" + return self.settings.get("use_pylock", False) + + @property + def pylock_output_path(self) -> str: + """Returns the path where pylock.toml should be written.""" + pylock_name = self.settings.get("pylock_name") + if pylock_name: + return str(Path(self.project_directory) / f"pylock.{pylock_name}.toml") + return str(Path(self.project_directory) / "pylock.toml") + def write_lockfile(self, content): """Write out the lockfile.""" + # Always write the Pipfile.lock s = self._lockfile_encoder.encode(content) open_kwargs = {"newline": self._lockfile_newlines, "encoding": "utf-8"} with atomic_open_for_write(self.lockfile_location, **open_kwargs) as f: @@ -997,6 +1034,22 @@ def write_lockfile(self, content): if not s.endswith("\n"): f.write("\n") + # If use_pylock is enabled, also write a pylock.toml file + if self.use_pylock: + try: + from pipenv.utils.pylock import PylockFile + + pylock = PylockFile.from_lockfile( + lockfile_path=self.lockfile_location, + pylock_path=self.pylock_output_path, + ) + pylock.write() + err.print( + f"[bold green]Generated pylock.toml at {self.pylock_output_path}[/bold green]" + ) + except Exception as e: + err.print(f"[bold red]Error generating pylock.toml: {e}[/bold red]") + def pipfile_sources(self, expand_vars=True): if self.pipfile_is_empty or "source" not in self.parsed_pipfile: sources = [self.default_source] diff --git a/pipenv/routines/install.py b/pipenv/routines/install.py index 0c0dccc83b..eb88e47220 100644 --- a/pipenv/routines/install.py +++ b/pipenv/routines/install.py @@ -485,8 +485,13 @@ def do_install_dependencies( lockfile_category = get_lockfile_section_using_pipfile_category( pipfile_category ) + lockfile_type = ( + "pylock.toml" + if project.use_pylock and project.pylock_location + else "Pipfile.lock" + ) console.print( - f"Installing dependencies from Pipfile.lock [{lockfile_category}]" + f"Installing dependencies from {lockfile_type} [{lockfile_category}]" f"({lockfile['_meta'].get('hash', {}).get('sha256')[-6:]})...", style="bold", ) diff --git a/pipenv/utils/pylock.py b/pipenv/utils/pylock.py new file mode 100644 index 0000000000..49d8a57e97 --- /dev/null +++ b/pipenv/utils/pylock.py @@ -0,0 +1,752 @@ +""" +PEP 751 pylock.toml file handling utilities. + +This module provides functionality for reading and parsing pylock.toml files +as specified in PEP 751 (A file format to record Python dependencies for +installation reproducibility). +""" + +import datetime +import json +import os +from dataclasses import dataclass, field +from pathlib import Path +from typing import Any, Dict, List, Optional, Set, Union + +from pipenv.utils import err +from pipenv.utils.locking import atomic_open_for_write +from pipenv.vendor import tomlkit + + +class PylockError(Exception): + """Base exception for pylock.toml related errors.""" + + pass + + +class PylockVersionError(PylockError): + """Raised when the lock-version is not supported.""" + + pass + + +class PylockFormatError(PylockError): + """Raised when the pylock.toml file format is invalid.""" + + pass + + +@dataclass +class PylockFile: + """Represents a pylock.toml file as specified in PEP 751.""" + + path: Path + data: Dict[str, Any] = field(default_factory=dict) + + @classmethod + def from_lockfile( + cls, + lockfile_path: Union[str, Path], + pylock_path: Union[str, Path] = None, + dev_groups: Optional[List[str]] = None, + ) -> "PylockFile": + """Create a PylockFile from a Pipfile.lock file. + + Args: + lockfile_path: Path to the Pipfile.lock file + pylock_path: Path to save the pylock.toml file, defaults to pylock.toml in the same directory + dev_groups: List of dependency group names for develop packages (default: ['dev']) + + Returns: + A PylockFile instance + + Raises: + FileNotFoundError: If the Pipfile.lock file doesn't exist + ValueError: If the Pipfile.lock file is invalid + """ + if isinstance(lockfile_path, str): + lockfile_path = Path(lockfile_path) + + if not lockfile_path.exists(): + raise FileNotFoundError(f"Pipfile.lock not found: {lockfile_path}") + + if pylock_path is None: + pylock_path = lockfile_path.parent / "pylock.toml" + elif isinstance(pylock_path, str): + pylock_path = Path(pylock_path) + + # Default dev groups + if dev_groups is None: + dev_groups = ["dev"] + + try: + with open(lockfile_path, encoding="utf-8") as f: + lockfile_data = json.load(f) + except Exception as e: + raise ValueError(f"Invalid Pipfile.lock file: {e}") + + # Create the basic pylock.toml structure + pylock_data = { + "lock-version": "1.0", + "environments": [], + "extras": [], + "dependency-groups": dev_groups.copy(), # Include dev groups + "default-groups": ["default"], # Default group for non-dev packages + "created-by": "pipenv", + "packages": [], + } + + # Add Python version requirement if present + meta = lockfile_data.get("_meta", {}) + requires = meta.get("requires", {}) + if "python_version" in requires: + pylock_data["requires-python"] = f">={requires['python_version']}" + elif "python_full_version" in requires: + pylock_data["requires-python"] = f"=={requires['python_full_version']}" + + # Ensure all values are properly formatted for TOML + # Convert None values to empty strings or arrays + for key in ["environments", "extras", "dependency-groups", "default-groups"]: + if key in pylock_data and pylock_data[key] is None: + pylock_data[key] = [] + + # Extract sources and build index URL map + sources = meta.get("sources", []) + index_url_map = {} # Map source name to URL + default_index = "https://pypi.org/simple/" + if sources: + for source in sources: + source_name = source.get("name", "") + source_url = source.get("url", "") + if source_name and source_url: + # Convert to simple API URL format + index_url_map[source_name] = source_url.rstrip("/") + "/" + # Use the first source as default + if sources and sources[0].get("url"): + default_index = sources[0]["url"].rstrip("/") + "/" + + # Build dev marker expression: 'group1' in dependency_groups or 'group2' in ... + dev_marker = " or ".join(f"'{g}' in dependency_groups" for g in dev_groups) + + # Process packages + for section in ["default", "develop"]: + packages = lockfile_data.get(section, {}) + for name, package_data in packages.items(): + package = {"name": name} + + # Add version if present and not a wildcard + if "version" in package_data: + version = package_data["version"] + if version == "*": + # Skip wildcard versions - they don't belong in pylock.toml + pass + elif version.startswith("=="): + package["version"] = version[2:] + else: + package["version"] = version + + # Add markers if present + # PEP 751 marker syntax: 'group' in dependency_groups + if "markers" in package_data: + # For develop packages, add dependency_groups marker + if section == "develop": + package["marker"] = ( + f"({dev_marker}) and ({package_data['markers']})" + ) + else: + package["marker"] = package_data["markers"] + elif section == "develop": + package["marker"] = dev_marker + + # Add package index URL (PEP 751 packages.index) + # Use the index from package_data if specified, otherwise default + if "index" in package_data: + index_name = package_data["index"] + package["index"] = index_url_map.get(index_name, default_index) + else: + package["index"] = default_index + + # Add hashes if present, with proper wheel/sdist structure + if "hashes" in package_data: + wheels = [] + for hash_value in package_data["hashes"]: + if hash_value.startswith("sha256:"): + hash_hex = hash_value[7:] # Remove "sha256:" prefix + version_str = package.get("version", "0.0.0") + wheel_name = ( + f"{name.replace('-', '_')}-{version_str}-py3-none-any.whl" + ) + wheel = { + "name": wheel_name, + "url": f"{package['index']}{name}/{wheel_name}", + "hashes": {"sha256": hash_hex}, + } + wheels.append(wheel) + if wheels: + package["wheels"] = wheels + + pylock_data["packages"].append(package) + + # Add tool.pipenv section with metadata + pylock_data["tool"] = { + "pipenv": { + "generated_from": "Pipfile.lock", + "generation_date": datetime.datetime.now( + datetime.timezone.utc + ).isoformat(), + } + } + + instance = cls(path=pylock_path, data=pylock_data) + return instance + + @classmethod + def from_path(cls, path: Union[str, Path]) -> "PylockFile": + """Load a pylock.toml file from the given path. + + Args: + path: Path to the pylock.toml file + + Returns: + A PylockFile instance + + Raises: + FileNotFoundError: If the file doesn't exist + PylockFormatError: If the file is not a valid pylock.toml file + PylockVersionError: If the lock-version is not supported + """ + if isinstance(path, str): + path = Path(path) + + if not path.exists(): + raise FileNotFoundError(f"Pylock file not found: {path}") + + try: + with open(path, encoding="utf-8") as f: + content = f.read() + data = tomlkit.parse(content) + # Convert tomlkit objects to Python native types + data_dict = {} + for key, value in data.items(): + if isinstance( + value, + (tomlkit.items.Table, tomlkit.items.AoT, tomlkit.items.Array), + ): + data_dict[key] = value.unwrap() + else: + data_dict[key] = value + except Exception as e: + raise PylockFormatError(f"Invalid pylock.toml file: {e}") + + # Validate lock-version + lock_version = data_dict.get("lock-version") + if not lock_version: + raise PylockFormatError("Missing required field: lock-version") + + # Currently, we only support version 1.0 + if lock_version != "1.0": + raise PylockVersionError( + f"Unsupported lock-version: {lock_version}. Only version 1.0 is supported." + ) + + return cls(path=path, data=data_dict) + + @classmethod + def from_pyproject( + cls, + pyproject_path: Union[str, Path], + pylock_path: Union[str, Path] = None, + ) -> "PylockFile": + """Create a PylockFile skeleton from a pyproject.toml file. + + This reads the dependencies from pyproject.toml and creates a pylock.toml + structure. Note: This does NOT resolve dependencies - it only reads + the declared dependencies. Use a locker to resolve and populate full details. + + Args: + pyproject_path: Path to the pyproject.toml file + pylock_path: Path to save the pylock.toml file + + Returns: + A PylockFile instance with declared dependencies (unresolved) + + Raises: + FileNotFoundError: If the pyproject.toml file doesn't exist + ValueError: If the pyproject.toml file is invalid + """ + if isinstance(pyproject_path, str): + pyproject_path = Path(pyproject_path) + + if not pyproject_path.exists(): + raise FileNotFoundError(f"pyproject.toml not found: {pyproject_path}") + + if pylock_path is None: + pylock_path = pyproject_path.parent / "pylock.toml" + elif isinstance(pylock_path, str): + pylock_path = Path(pylock_path) + + try: + with open(pyproject_path, encoding="utf-8") as f: + pyproject_data = tomlkit.parse(f.read()) + except Exception as e: + raise ValueError(f"Invalid pyproject.toml file: {e}") + + # Extract project metadata (PEP 621) + project = pyproject_data.get("project", {}) + project_name = project.get("name", "") + requires_python = project.get("requires-python", "") + + # Create the basic pylock.toml structure + pylock_data: Dict[str, Any] = { + "lock-version": "1.0", + "environments": [], + "created-by": "pipenv", + "packages": [], + } + + if requires_python: + pylock_data["requires-python"] = requires_python + + # Extract dependencies from [project.dependencies] (PEP 621) + dependencies = project.get("dependencies", []) + + # Extract optional dependencies for extras (PEP 621) + optional_deps = project.get("optional-dependencies", {}) + extras = list(optional_deps.keys()) + if extras: + pylock_data["extras"] = extras + + # Extract dependency groups (PEP 735) + dependency_groups_data = pyproject_data.get("dependency-groups", {}) + dependency_groups = list(dependency_groups_data.keys()) + if dependency_groups: + pylock_data["dependency-groups"] = dependency_groups + + # Default groups (main dependencies) + pylock_data["default-groups"] = ["default"] if dependencies else [] + + # Parse main dependencies + for dep in dependencies: + package = cls._parse_dependency_string(dep) + if package: + pylock_data["packages"].append(package) + + # Parse optional dependencies (extras) + for extra_name, extra_deps in optional_deps.items(): + for dep in extra_deps: + package = cls._parse_dependency_string(dep) + if package: + # Add marker for extra + existing_marker = package.get("marker", "") + extra_marker = f"'{extra_name}' in extras" + if existing_marker: + package["marker"] = f"({extra_marker}) and ({existing_marker})" + else: + package["marker"] = extra_marker + pylock_data["packages"].append(package) + + # Parse dependency groups (PEP 735) + for group_name, group_deps in dependency_groups_data.items(): + for dep in group_deps: + # Skip include directives like {include-group = "..."} + if isinstance(dep, dict): + continue + package = cls._parse_dependency_string(dep) + if package: + # Add marker for dependency group + existing_marker = package.get("marker", "") + group_marker = f"'{group_name}' in dependency_groups" + if existing_marker: + package["marker"] = f"({group_marker}) and ({existing_marker})" + else: + package["marker"] = group_marker + pylock_data["packages"].append(package) + + # Add tool.pipenv section with metadata + pylock_data["tool"] = { + "pipenv": { + "generated_from": "pyproject.toml", + "project_name": project_name, + "generation_date": datetime.datetime.now( + datetime.timezone.utc + ).isoformat(), + } + } + + return cls(path=pylock_path, data=pylock_data) + + @staticmethod + def _parse_dependency_string(dep_string: str) -> Optional[Dict[str, Any]]: + """Parse a PEP 508 dependency string into a package dict. + + Args: + dep_string: A PEP 508 dependency specifier (e.g., "requests>=2.28.0") + + Returns: + A dict with 'name' and optionally 'marker', or None if parsing fails + """ + if not dep_string or not isinstance(dep_string, str): + return None + + try: + # Use pip's requirement parser + from pipenv.patched.pip._vendor.packaging.requirements import Requirement + + req = Requirement(dep_string) + package: Dict[str, Any] = {"name": req.name} + + # Add marker if present + if req.marker: + package["marker"] = str(req.marker) + + return package + except Exception: + # Fallback: simple name extraction + import re + + match = re.match(r"^([a-zA-Z0-9][-a-zA-Z0-9._]*)", dep_string) + if match: + return {"name": match.group(1).lower()} + return None + + def write(self) -> None: + """Write the pylock.toml file to disk. + + Raises: + OSError: If there is an error writing the file + """ + try: + # Ensure all values are properly formatted for TOML + # Create a deep copy of the data to avoid modifying the original + data_copy = {} + for key, value in self.data.items(): + if isinstance(value, dict): + data_copy[key] = value.copy() + elif isinstance(value, list): + data_copy[key] = value.copy() + else: + data_copy[key] = value + + # Convert None values to empty strings or arrays + for key in ["environments", "extras", "dependency-groups", "default-groups"]: + if key in data_copy: + if data_copy[key] is None: + data_copy[key] = [] + + # Convert the data to a TOML document + doc = tomlkit.document() + + # Add top-level keys in a specific order for readability + for key in [ + "lock-version", + "environments", + "requires-python", + "extras", + "dependency-groups", + "default-groups", + "created-by", + ]: + if key in data_copy: + doc[key] = data_copy[key] + + # Add packages + if "packages" in data_copy: + doc["packages"] = tomlkit.aot() + for package in data_copy["packages"]: + pkg_table = tomlkit.table() + + # Add basic package info first for better readability + for key in ["name", "version", "marker", "requires-python"]: + if key in package: + pkg_table[key] = package[key] + + # Add remaining keys except wheels and sdist + for k, v in package.items(): + if k not in { + "name", + "version", + "marker", + "requires-python", + "wheels", + "sdist", + }: + pkg_table[k] = v + + # Add wheels as an array of tables with better formatting + if "wheels" in package: + wheels_array = tomlkit.array() + wheels_array.multiline(True) + + for wheel in package["wheels"]: + wheel_table = tomlkit.inline_table() + + # Add wheel properties in a specific order + for key in ["name", "upload-time", "url", "size"]: + if key in wheel: + wheel_table[key] = wheel[key] + + # Add hashes as a table + if "hashes" in wheel: + hashes_table = tomlkit.inline_table() + for hash_algo, hash_value in wheel["hashes"].items(): + hashes_table[hash_algo] = hash_value + wheel_table["hashes"] = hashes_table + + wheels_array.append(wheel_table) + + pkg_table["wheels"] = wheels_array + + # Add sdist as a table + if "sdist" in package: + sdist_table = tomlkit.inline_table() + + # Add sdist properties in a specific order + for key in ["name", "upload-time", "url", "size"]: + if key in package["sdist"]: + sdist_table[key] = package["sdist"][key] + + # Add hashes as a table + if "hashes" in package["sdist"]: + hashes_table = tomlkit.inline_table() + for hash_algo, hash_value in package["sdist"][ + "hashes" + ].items(): + hashes_table[hash_algo] = hash_value + sdist_table["hashes"] = hashes_table + + pkg_table["sdist"] = sdist_table + + doc["packages"].append(pkg_table) + + # Add tool section + if "tool" in data_copy: + tool_table = tomlkit.table() + for tool_name, tool_data in data_copy["tool"].items(): + tool_section = tomlkit.table() + for k, v in tool_data.items(): + tool_section[k] = v + tool_table[tool_name] = tool_section + doc["tool"] = tool_table + + # Write the document to the file with proper formatting + with atomic_open_for_write(self.path, encoding="utf-8") as f: + content = tomlkit.dumps(doc) + # Ensure there's a blank line between package entries for readability + content = content.replace("[[packages]]\n", "\n[[packages]]\n") + f.write(content) + + except Exception as e: + err.print(f"[bold red]Error writing pylock.toml: {e}[/bold red]") + raise OSError(f"Error writing pylock.toml: {e}") + + @property + def lock_version(self) -> str: + """Get the lock-version.""" + return self.data.get("lock-version", "") + + @property + def environments(self) -> List[str]: + """Get the environments list.""" + return self.data.get("environments", []) + + @property + def requires_python(self) -> Optional[str]: + """Get the requires-python value.""" + return self.data.get("requires-python") + + @property + def extras(self) -> List[str]: + """Get the extras list.""" + return self.data.get("extras", []) + + @property + def dependency_groups(self) -> List[str]: + """Get the dependency-groups list.""" + return self.data.get("dependency-groups", []) + + @property + def default_groups(self) -> List[str]: + """Get the default-groups list.""" + return self.data.get("default-groups", []) + + @property + def created_by(self) -> str: + """Get the created-by value.""" + return self.data.get("created-by", "") + + @property + def packages(self) -> List[Dict[str, Any]]: + """Get the packages list.""" + return self.data.get("packages", []) + + @property + def tool(self) -> Dict[str, Any]: + """Get the tool table.""" + return self.data.get("tool", {}) + + def get_packages_for_environment( + self, + extras: Optional[Set[str]] = None, + dependency_groups: Optional[Set[str]] = None, + ) -> List[Dict[str, Any]]: + """Get packages that should be installed for the given environment. + + Args: + extras: Set of extras to include + dependency_groups: Set of dependency groups to include + + Returns: + List of package dictionaries that should be installed + """ + from pipenv.patched.pip._vendor.packaging.markers import ( + InvalidMarker, + Marker, + ) + + # Set up extras and dependency_groups for marker evaluation + _extras = frozenset(extras) if extras is not None else frozenset() + _dependency_groups = ( + frozenset(dependency_groups) + if dependency_groups is not None + else frozenset(self.default_groups) + ) + + result = [] + + for package in self.packages: + # Check if the package has a marker + marker_str = package.get("marker") + if marker_str: + try: + marker = Marker(marker_str) + # Evaluate the marker with the lock_file context + # which supports extras and dependency_groups as sets + environment = { + "extras": _extras, + "dependency_groups": _dependency_groups, + } + if not marker.evaluate(environment=environment, context="lock_file"): + # Marker does not match, skip this package + continue + except InvalidMarker: + # If the marker is invalid, include the package anyway + # to be safe and let the installer handle it + pass + + result.append(package) + + return result + + def convert_to_pipenv_lockfile(self) -> Dict[str, Any]: + """Convert the pylock.toml file to a Pipfile.lock format. + + Returns: + A dictionary in Pipfile.lock format + """ + # Create the basic structure + lockfile = { + "_meta": { + "hash": {"sha256": ""}, # We don't have a hash in pylock.toml + "pipfile-spec": 6, + "requires": {}, + "sources": [], + }, + "default": {}, + "develop": {}, + } + + # Add Python version requirement if present + if self.requires_python: + lockfile["_meta"]["requires"]["python_version"] = self.requires_python + + # Add sources if present + if "sources" in self.data: + lockfile["_meta"]["sources"] = self.data["sources"] + # If no sources in pylock.toml, add a default source + else: + lockfile["_meta"]["sources"] = [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": True, + } + ] + + # Process packages + for package in self.packages: + name = package.get("name") + if not name: + continue + + # Determine if this is a dev package based on markers + # This is a simplification - in reality we'd need to parse the markers + is_dev = False + marker = package.get("marker", "") + if marker and "dependency_groups" in marker: + # Simple heuristic - if it mentions dev or test, it's probably a dev package + if "dev" in marker.lower() or "test" in marker.lower(): + is_dev = True + + # Create the package entry + package_entry = {} + + # Add version if present, otherwise use wildcard for Pipfile.lock compatibility + if "version" in package: + package_entry["version"] = f"=={package['version']}" + else: + # No version in pylock.toml means any version is acceptable + package_entry["version"] = "*" + + # Add hashes if present + hashes = [] + if "wheels" in package: + hashes.extend( + f"sha256:{wheel['hashes']['sha256']}" + for wheel in package["wheels"] + if "hashes" in wheel and "sha256" in wheel["hashes"] + ) + if ( + "sdist" in package + and "hashes" in package["sdist"] + and "sha256" in package["sdist"]["hashes"] + ): + hashes.append(f"sha256:{package['sdist']['hashes']['sha256']}") + if hashes: + package_entry["hashes"] = hashes + + # Add marker if present + if marker: + package_entry["markers"] = marker + + # Add to the appropriate section + section = "develop" if is_dev else "default" + lockfile[section][name] = package_entry + + return lockfile + + +def find_pylock_file(directory: Union[str, Path] = None) -> Optional[Path]: + """Find a pylock.toml file in the given directory. + + Args: + directory: Directory to search in, defaults to current directory + + Returns: + Path to the pylock.toml file if found, None otherwise + """ + if directory is None: + directory = os.getcwd() + + if isinstance(directory, str): + directory = Path(directory) + + # First, look for pylock.toml + pylock_path = directory / "pylock.toml" + if pylock_path.exists(): + return pylock_path + + # Then, look for named pylock files (pylock.*.toml) + for file in directory.glob("pylock.*.toml"): + return file + + return None diff --git a/pipenv/utils/requirements.py b/pipenv/utils/requirements.py index 1c018fad9b..63c5dfc6dd 100644 --- a/pipenv/utils/requirements.py +++ b/pipenv/utils/requirements.py @@ -220,6 +220,9 @@ def requirement_from_lockfile( # Handling packages from standard pypi like indexes version = package_info.get("version", "") + # Skip wildcard versions - they mean "any version" and should not be included + if is_star(version): + version = "" hashes = ( f" --hash={' --hash='.join(package_info['hashes'])}" if include_hashes and "hashes" in package_info diff --git a/pylock.toml b/pylock.toml new file mode 100644 index 0000000000..46f4aca712 --- /dev/null +++ b/pylock.toml @@ -0,0 +1,1594 @@ +lock-version = "1.0" +environments = [] +extras = [] +dependency-groups = [] +default-groups = [] +created-by = "pipenv" + +[[packages]] +name = "pytz" +version = "2025.2" +wheels = [ + {name = "pytz-2025.2-py3-none-any.whl", hashes = {sha256 = "360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3"}}, + {name = "pytz-2025.2-py3-none-any.whl", hashes = {sha256 = "5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00"}}, +] + + +[[packages]] +name = "alabaster" +version = "1.0.0" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.10')" +wheels = [ + {name = "alabaster-1.0.0-py3-none-any.whl", hashes = {sha256 = "c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e"}}, + {name = "alabaster-1.0.0-py3-none-any.whl", hashes = {sha256 = "fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b"}}, +] + + +[[packages]] +name = "arpeggio" +version = "2.0.3" +marker = "dependency_groups in ('dev', 'test')" +wheels = [ + {name = "arpeggio-2.0.3-py3-none-any.whl", hashes = {sha256 = "9374d9c531b62018b787635f37fd81c9a6ee69ef2d28c5db3cd18791b1f7db2f"}}, + {name = "arpeggio-2.0.3-py3-none-any.whl", hashes = {sha256 = "9e85ad35cfc6c938676817c7ae9a1000a7c72a34c71db0c687136c460d12b85e"}}, +] + + +[[packages]] +name = "atomicwrites" +version = "1.4.1" +marker = "dependency_groups in ('dev', 'test') and (sys_platform == 'win32')" +wheels = [ + {name = "atomicwrites-1.4.1-py3-none-any.whl", hashes = {sha256 = "81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}}, +] + + +[[packages]] +name = "attrs" +version = "25.4.0" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.9')" +wheels = [ + {name = "attrs-25.4.0-py3-none-any.whl", hashes = {sha256 = "16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11"}}, + {name = "attrs-25.4.0-py3-none-any.whl", hashes = {sha256 = "adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373"}}, +] + + +[[packages]] +name = "babel" +version = "2.17.0" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.8')" +wheels = [ + {name = "babel-2.17.0-py3-none-any.whl", hashes = {sha256 = "0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d"}}, + {name = "babel-2.17.0-py3-none-any.whl", hashes = {sha256 = "4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2"}}, +] + + +[[packages]] +name = "backports.tarfile" +version = "1.2.0" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.8')" +wheels = [ + {name = "backports.tarfile-1.2.0-py3-none-any.whl", hashes = {sha256 = "77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34"}}, + {name = "backports.tarfile-1.2.0-py3-none-any.whl", hashes = {sha256 = "d75e02c268746e1b8144c278978b6e98e85de6ad16f8e4b0844a154557eca991"}}, +] + + +[[packages]] +name = "beautifulsoup4" +version = "4.14.3" +marker = "dependency_groups in ('dev', 'test') and (python_full_version >= '3.7.0')" +wheels = [ + {name = "beautifulsoup4-4.14.3-py3-none-any.whl", hashes = {sha256 = "0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb"}}, + {name = "beautifulsoup4-4.14.3-py3-none-any.whl", hashes = {sha256 = "6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86"}}, +] + + +[[packages]] +name = "black" +version = "24.3.0" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.8')" +wheels = [ + {name = "black-24.3.0-py3-none-any.whl", hashes = {sha256 = "2818cf72dfd5d289e48f37ccfa08b460bf469e67fb7c4abb07edc2e9f16fb63f"}}, + {name = "black-24.3.0-py3-none-any.whl", hashes = {sha256 = "41622020d7120e01d377f74249e677039d20e6344ff5851de8a10f11f513bf93"}}, + {name = "black-24.3.0-py3-none-any.whl", hashes = {sha256 = "4acf672def7eb1725f41f38bf6bf425c8237248bb0804faa3965c036f7672d11"}}, + {name = "black-24.3.0-py3-none-any.whl", hashes = {sha256 = "4be5bb28e090456adfc1255e03967fb67ca846a03be7aadf6249096100ee32d0"}}, + {name = "black-24.3.0-py3-none-any.whl", hashes = {sha256 = "4f1373a7808a8f135b774039f61d59e4be7eb56b2513d3d2f02a8b9365b8a8a9"}}, + {name = "black-24.3.0-py3-none-any.whl", hashes = {sha256 = "56f52cfbd3dabe2798d76dbdd299faa046a901041faf2cf33288bc4e6dae57b5"}}, + {name = "black-24.3.0-py3-none-any.whl", hashes = {sha256 = "65b76c275e4c1c5ce6e9870911384bff5ca31ab63d19c76811cb1fb162678213"}}, + {name = "black-24.3.0-py3-none-any.whl", hashes = {sha256 = "65c02e4ea2ae09d16314d30912a58ada9a5c4fdfedf9512d23326128ac08ac3d"}}, + {name = "black-24.3.0-py3-none-any.whl", hashes = {sha256 = "6905238a754ceb7788a73f02b45637d820b2f5478b20fec82ea865e4f5d4d9f7"}}, + {name = "black-24.3.0-py3-none-any.whl", hashes = {sha256 = "79dcf34b33e38ed1b17434693763301d7ccbd1c5860674a8f871bd15139e7837"}}, + {name = "black-24.3.0-py3-none-any.whl", hashes = {sha256 = "7bb041dca0d784697af4646d3b62ba4a6b028276ae878e53f6b4f74ddd6db99f"}}, + {name = "black-24.3.0-py3-none-any.whl", hashes = {sha256 = "7d5e026f8da0322b5662fa7a8e752b3fa2dac1c1cbc213c3d7ff9bdd0ab12395"}}, + {name = "black-24.3.0-py3-none-any.whl", hashes = {sha256 = "9f50ea1132e2189d8dff0115ab75b65590a3e97de1e143795adb4ce317934995"}}, + {name = "black-24.3.0-py3-none-any.whl", hashes = {sha256 = "a0c9c4a0771afc6919578cec71ce82a3e31e054904e7197deacbc9382671c41f"}}, + {name = "black-24.3.0-py3-none-any.whl", hashes = {sha256 = "aadf7a02d947936ee418777e0247ea114f78aff0d0959461057cae8a04f20597"}}, + {name = "black-24.3.0-py3-none-any.whl", hashes = {sha256 = "b5991d523eee14756f3c8d5df5231550ae8993e2286b8014e2fdea7156ed0959"}}, + {name = "black-24.3.0-py3-none-any.whl", hashes = {sha256 = "bf21b7b230718a5f08bd32d5e4f1db7fc8788345c8aea1d155fc17852b3410f5"}}, + {name = "black-24.3.0-py3-none-any.whl", hashes = {sha256 = "c45f8dff244b3c431b36e3224b6be4a127c6aca780853574c00faf99258041eb"}}, + {name = "black-24.3.0-py3-none-any.whl", hashes = {sha256 = "c7ed6668cbbfcd231fa0dc1b137d3e40c04c7f786e626b405c62bcd5db5857e4"}}, + {name = "black-24.3.0-py3-none-any.whl", hashes = {sha256 = "d7de8d330763c66663661a1ffd432274a2f92f07feeddd89ffd085b5744f85e7"}}, + {name = "black-24.3.0-py3-none-any.whl", hashes = {sha256 = "e19cb1c6365fd6dc38a6eae2dcb691d7d83935c10215aef8e6c38edee3f77abd"}}, + {name = "black-24.3.0-py3-none-any.whl", hashes = {sha256 = "e2af80566f43c85f5797365077fb64a393861a3730bd110971ab7a0c94e873e7"}}, +] + + +[[packages]] +name = "bottle" +version = "0.13.4" +marker = "dependency_groups in ('dev', 'test')" +wheels = [ + {name = "bottle-0.13.4-py3-none-any.whl", hashes = {sha256 = "045684fbd2764eac9cdeb824861d1551d113e8b683d8d26e296898d3dd99a12e"}}, + {name = "bottle-0.13.4-py3-none-any.whl", hashes = {sha256 = "787e78327e12b227938de02248333d788cfe45987edca735f8f88e03472c3f47"}}, +] + + +[[packages]] +name = "build" +version = "1.3.0" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.9')" +wheels = [ + {name = "build-1.3.0-py3-none-any.whl", hashes = {sha256 = "698edd0ea270bde950f53aed21f3a0135672206f3911e0176261a31e0e07b397"}}, + {name = "build-1.3.0-py3-none-any.whl", hashes = {sha256 = "7145f0b5061ba90a1500d60bd1b13ca0a8a4cebdd0cc16ed8adf1c0e739f43b4"}}, +] + + +[[packages]] +name = "certifi" +version = "2025.11.12" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.7')" +wheels = [ + {name = "certifi-2025.11.12-py3-none-any.whl", hashes = {sha256 = "97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b"}}, + {name = "certifi-2025.11.12-py3-none-any.whl", hashes = {sha256 = "d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316"}}, +] + + +[[packages]] +name = "cffi" +version = "2.0.0" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.9')" +wheels = [ + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "cb527a79772e5ef98fb1d700678fe031e353e765d1ca2d409c92263c6d43e09f"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "e6e73b9e02893c764e7e8d5bb5ce277f1a009cd5243f8228f75f842bf937c534"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453"}}, + {name = "cffi-2.0.0-py3-none-any.whl", hashes = {sha256 = "fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf"}}, +] + + +[[packages]] +name = "cfgv" +version = "3.5.0" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.10')" +wheels = [ + {name = "cfgv-3.5.0-py3-none-any.whl", hashes = {sha256 = "a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0"}}, + {name = "cfgv-3.5.0-py3-none-any.whl", hashes = {sha256 = "d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132"}}, +] + + +[[packages]] +name = "charset-normalizer" +version = "3.4.4" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.7')" +wheels = [ + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "194f08cbb32dc406d6e1aea671a68be0823673db2832b38405deba2fb0d88f63"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "2aaba3b0819274cc41757a1da876f810a3e4d7b6eb25699253a4effef9e8e4af"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "44c2a8734b333e0578090c4cd6b16f275e07aa6614ca8715e6c038e865e70576"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "5cb4d72eea50c8868f5288b7f7f33ed276118325c1dfd3957089f6b519e1382a"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "6aee717dcfead04c6eb1ce3bd29ac1e22663cdea57f943c87d1eab9a025438d7"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "778d2e08eda00f4256d7f672ca9fef386071c9202f5e4607920b86d7803387f2"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "837c2ce8c5a65a2035be9b3569c684358dfbf109fd3b6969630a87535495ceaa"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "a8bf8d0f749c5757af2142fe7903a9df1d2e8aa3841559b2bad34b08d0e2bcf3"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "b7cf1017d601aa35e6bb650b6ad28652c9cd78ee6caff19f3c28d03e1c80acbf"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "c4ef880e27901b6cc782f1b95f82da9313c0eb95c3af699103088fa0ac3ce9ac"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "cd4b7ca9984e5e7985c12bc60a6f173f3c958eae74f3ef6624bb6b26e2abbae4"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "ce8a0633f41a967713a59c4139d29110c07e826d131a316b50ce11b1d79b4f84"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "e912091979546adf63357d7e2ccff9b44f026c075aeaf25a52d0e95ad2281074"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "eaabd426fe94daf8fd157c32e571c85cb12e66692f15516a83a03264b08d06c3"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "f155a433c2ec037d4e8df17d18922c3a0d9b3232a396690f17175d2946f0218d"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e"}}, + {name = "charset-normalizer-3.4.4-py3-none-any.whl", hashes = {sha256 = "fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608"}}, +] + + +[[packages]] +name = "click" +version = "8.0.3" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.6')" +wheels = [ + {name = "click-8.0.3-py3-none-any.whl", hashes = {sha256 = "353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"}}, + {name = "click-8.0.3-py3-none-any.whl", hashes = {sha256 = "410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"}}, +] + + +[[packages]] +name = "colorama" +version = "0.4.6" +marker = "dependency_groups in ('dev', 'test') and (sys_platform == 'win32')" +wheels = [ + {name = "colorama-0.4.6-py3-none-any.whl", hashes = {sha256 = "08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}}, + {name = "colorama-0.4.6-py3-none-any.whl", hashes = {sha256 = "4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}}, +] + + +[[packages]] +name = "coverage" +version = "7.13.0" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.10')" +wheels = [ + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "0018f73dfb4301a89292c73be6ba5f58722ff79f51593352759c1790ded1cabe"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "00c3d22cf6fb1cf3bf662aaaa4e563be8243a5ed2630339069799835a9cc7f9b"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "02d9fb9eccd48f6843c98a37bd6817462f130b86da8660461e8f5e54d4c06070"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "0602f701057c6823e5db1b74530ce85f17c3c5be5c85fc042ac939cbd909426e"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "06cac81bf10f74034e055e903f5f946e3e26fc51c09fc9f584e4a1605d977053"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "086cede306d96202e15a4b77ace8472e39d9f4e5f9fd92dd4fecdfb2313b2080"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "0900872f2fdb3ee5646b557918d02279dc3af3dfb39029ac4e945458b13f73bc"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "0a3a30f0e257df382f5f9534d4ce3d4cf06eafaf5192beb1a7bd066cb10e78fb"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "0b3d67d31383c4c68e19a88e28fc4c2e29517580f1b0ebec4a069d502ce1e0bf"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "0dfa3855031070058add1a59fdfda0192fd3e8f97e7c81de0596c145dea51820"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "0f4872f5d6c54419c94c25dd6ae1d015deeb337d06e448cd890a1e89a8ee7f3b"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "11c21557d0e0a5a38632cbbaca5f008723b26a89d70db6315523df6df77d6232"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "166ad2a22ee770f5656e1257703139d3533b4a0b6909af67c6b4a3adc1c98657"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "193c3887285eec1dbdb3f2bd7fbc351d570ca9c02ca756c3afbc71b3c98af6ef"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "1d84e91521c5e4cb6602fe11ece3e1de03b2760e14ae4fcf1a4b56fa3c801fcd"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "1ed5630d946859de835a85e9a43b721123a8a44ec26e2830b296d478c7fd4259"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "22486cdafba4f9e471c816a2a5745337742a617fef68e890d8baf9f3036d7833"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "22ccfe8d9bb0d6134892cbe1262493a8c70d736b9df930f3f3afae0fe3ac924d"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "24e4e56304fdb56f96f80eabf840eab043b3afea9348b88be680ec5986780a0f"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "25dc33618d45456ccb1d37bce44bc78cf269909aa14c4db2e03d63146a8a1493"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "263c3dbccc78e2e331e59e90115941b5f53e85cfcc6b3b2fbff1fd4e3d2c6ea8"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "28ee1c96109974af104028a8ef57cec21447d42d0e937c0275329272e370ebcf"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "30a3a201a127ea57f7e14ba43c93c9c4be8b7d17a26e03bb49e6966d019eede9"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "3188936845cd0cb114fa6a51842a304cdbac2958145d03be2377ec41eb285d19"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "367449cf07d33dc216c083f2036bb7d976c6e4903ab31be400ad74ad9f85ce98"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "37eee4e552a65866f15dedd917d5e5f3d59805994260720821e2c1b51ac3248f"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "3a10260e6a152e5f03f26db4a407c4c62d3830b9af9b7c0450b183615f05d43b"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "3a7b1cd820e1b6116f92c6128f1188e7afe421c7e1b35fa9836b11444e53ebd9"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "3ab483ea0e251b5790c2aac03acde31bff0c736bf8a86829b89382b407cd1c3b"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "3ad968d1e3aa6ce5be295ab5fe3ae1bf5bb4769d0f98a80a0252d543a2ef2e9e"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "445badb539005283825959ac9fa4a28f712c214b65af3a2c464f1adc90f5fcbc"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "453b7ec753cf5e4356e14fe858064e5520c460d3bbbcb9c35e55c0d21155c256"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "494f5459ffa1bd45e18558cd98710c36c0b8fbfa82a5eabcbe671d80ecffbfe8"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "4b5de7d4583e60d5fd246dd57fcd3a8aa23c6e118a8c72b38adf666ba8e7e927"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "4f3e223b2b2db5e0db0c2b97286aba0036ca000f06aca9b12112eaa9af3d92ae"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "4fdb6f54f38e334db97f72fa0c701e66d8479af0bc3f9bfb5b90f1c30f54500f"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "51a202e0f80f241ccb68e3e26e19ab5b3bf0f813314f2c967642f13ebcf1ddfe"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "581f086833d24a22c89ae0fe2142cfaa1c92c930adf637ddf122d55083fb5a0f"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "583221913fbc8f53b88c42e8dbb8fca1d0f2e597cb190ce45916662b8b9d9621"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "58632b187be6f0be500f553be41e277712baa278147ecb7559983c6d9faf7ae1"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "5c67dace46f361125e6b9cace8fe0b729ed8479f47e70c89b838d319375c8137"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "5e70f92ef89bac1ac8a99b3324923b4749f008fdbd7aa9cb35e01d7a284a04f9"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "5f5d9bd30756fff3e7216491a0d6d520c448d5124d3d8e8f56446d6412499e74"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "5f8a0297355e652001015e93be345ee54393e45dc3050af4a0475c5a2b767d46"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "62d7c4f13102148c78d7353c6052af6d899a7f6df66a32bddcc0c0eb7c5326f8"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "69ac2c492918c2461bc6ace42d0479638e60719f2a4ef3f0815fa2df88e9f940"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "6abb3a4c52f05e08460bd9acf04fec027f8718ecaa0d09c40ffbc3fbd70ecc39"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "6e63ccc6e0ad8986386461c3c4b737540f20426e7ec932f42e030320896c311a"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "6e9e451dee940a86789134b6b0ffbe31c454ade3b849bb8a9d2cca2541a8e91d"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "6fb2d5d272341565f08e962cce14cdf843a08ac43bd621783527adb06b089c4b"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "71936a8b3b977ddd0b694c28c6a34f4fff2e9dd201969a4ff5d5fc7742d614b0"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "73419b89f812f498aca53f757dd834919b48ce4799f9d5cad33ca0ae442bdb1a"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "739c6c051a7540608d097b8e13c76cfa85263ced467168dc6b477bae3df7d0e2"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "7464663eaca6adba4175f6c19354feea61ebbdd735563a03d1e472c7072d27bb"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "74c136e4093627cf04b26a35dab8cbfc9b37c647f0502fc313376e11726ba303"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "76541dc8d53715fb4f7a3a06b34b0dc6846e3c69bc6204c55653a85dd6220971"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "7a485ff48fbd231efa32d58f479befce52dcb6bfb2a88bb7bf9a0b89b1bc8030"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "7e442c013447d1d8d195be62852270b78b6e255b79b8675bad8479641e21fd96"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "7f15a931a668e58087bc39d05d2b4bf4b14ff2875b49c994bbdb1c2217a8daeb"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "7f88ae3e69df2ab62fb0bc5219a597cb890ba5c438190ffa87490b315190bb33"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "8069e831f205d2ff1f3d355e82f511eb7c5522d7d413f5db5756b772ec8697f8"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "850d2998f380b1e266459ca5b47bc9e7daf9af1d070f66317972f382d46f1904"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "898cce66d0836973f48dda4e3514d863d70142bdf6dfab932b9b6a90ea5b222d"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "9097818b6cc1cfb5f174e3263eba4a62a17683bcfe5c4b5d07f4c97fa51fbf28"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "936bc20503ce24770c71938d1369461f0c5320830800933bc3956e2a4ded930e"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "9372dff5ea15930fea0445eaf37bbbafbc771a49e70c0aeed8b4e2c2614cc00e"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "9987a9e4f8197a1000280f7cc089e3ea2c8b3c0a64d750537809879a7b4ceaf9"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "99acd4dfdfeb58e1937629eb1ab6ab0899b131f183ee5f23e0b5da5cba2fec74"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "9b01c22bc74a7fb44066aaf765224c0d933ddf1f5047d6cdfe4795504a4493f8"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "a00d3a393207ae12f7c49bb1c113190883b500f48979abb118d8b72b8c95c032"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "a23e5a1f8b982d56fa64f8e442e037f6ce29322f1f9e6c2344cd9e9f4407ee57"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "a2bdb3babb74079f021696cb46b8bb5f5661165c385d3a238712b031a12355be"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "a394aa27f2d7ff9bc04cf703817773a59ad6dfbd577032e690f961d2460ee936"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "a6c6e16b663be828a8f0b6c5027d36471d4a9f90d28444aa4ced4d48d7d6ae8f"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "af0a583efaacc52ae2521f8d7910aff65cdb093091d76291ac5820d5e947fc1c"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "af827b7cbb303e1befa6c4f94fd2bf72f108089cfa0f8abab8f4ca553cf5ca5a"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "c4be718e51e86f553bcf515305a158a1cd180d23b72f07ae76d6017c3cc5d791"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "cdb3c9f8fef0a954c632f64328a3935988d33a6604ce4bf67ec3e39670f12ae5"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "d10fd186aac2316f9bbb46ef91977f9d394ded67050ad6d84d94ed6ea2e8e54e"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "d1e97353dcc5587b85986cda4ff3ec98081d7e84dd95e8b2a6d59820f0545f8a"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "d2a9d7f1c11487b1c69367ab3ac2d81b9b3721f097aa409a3191c3e90f8f3dd7"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "de7f6748b890708578fc4b7bb967d810aeb6fcc9bff4bb77dbca77dab2f9df6a"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "e5330fa0cc1f5c3c4c3bb8e101b742025933e7848989370a1d4c8c5e401ea753"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "e999e2dcc094002d6e2c7bbc1fb85b58ba4f465a760a8014d97619330cdbbbf3"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "eb76670874fdd6091eedcc856128ee48c41a9bbbb9c3f1c7c3cf169290e3ffd6"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "f1c23e24a7000da892a312fb17e33c5f94f8b001de44b7cf8ba2e36fbd15859e"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "f2ffc92b46ed6e6760f1d47a71e56b5664781bc68986dbd1836b2b70c0ce2071"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "f4f72a85316d8e13234cafe0a9f81b40418ad7a082792fa4165bd7d45d96066b"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "f59883c643cb19630500f57016f76cfdcd6845ca8c5b5ea1f6e17f74c8e5f511"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "f6aaef16d65d1787280943f1c8718dc32e9cf141014e4634d64446702d26e0ff"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "fe81055d8c6c9de76d60c94ddea73c290b416e061d40d542b24a5871bad498b7"}}, + {name = "coverage-7.13.0-py3-none-any.whl", hashes = {sha256 = "ff45e0cd8451e293b63ced93161e189780baf444119391b3e7d25315060368a6"}}, +] + + +[[packages]] +name = "cryptography" +version = "46.0.3" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.8' and python_full_version not in '3.9.0, 3.9.1')" +wheels = [ + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "00a5e7e87938e5ff9ff5447ab086a5706a957137e6e433841e9d24f38a065217"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "0abf1ffd6e57c67e92af68330d05760b7b7efb243aab8377e583284dbab72c71"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "1000713389b75c449a6e979ffc7dcc8ac90b437048766cef052d4d30b8220971"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "109d4ddfadf17e8e7779c39f9b18111a09efb969a301a31e987416a0191ed93a"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "10b01676fc208c3e6feeb25a8b83d81767e8059e1fe86e1dc62d10a3018fa926"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "10ca84c4668d066a9878890047f03546f3ae0a6b8b39b697457b7757aaf18dbc"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "15ab9b093e8f09daab0f2159bb7e47532596075139dd74365da52ecc9cb46c5d"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "191bb60a7be5e6f54e30ba16fdfae78ad3a342a0599eb4193ba88e3f3d6e185b"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "22d7e97932f511d6b0b04f2bfd818d73dcd5928db509460aaf48384778eb6d20"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "23b1a8f26e43f47ceb6d6a43115f33a5a37d57df4ea0ca295b780ae8546e8044"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "36e627112085bb3b81b19fed209c05ce2a52ee8b15d161b7c643a7d5a88491f3"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "39b6755623145ad5eff1dab323f4eae2a32a77a7abef2c5089a04a3d04366715"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "3b51b8ca4f1c6453d8829e1eb7299499ca7f313900dd4d89a24b8b87c0a780d4"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "402b58fc32614f00980b66d6e56a5b4118e6cb362ae8f3fda141ba4689bd4506"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "416260257577718c05135c55958b674000baef9a1c7d9e8f306ec60d71db850f"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "46acf53b40ea38f9c6c229599a4a13f0d46a6c3fa9ef19fc1a124d62e338dfa0"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "4b7387121ac7d15e550f5cb4a43aef2559ed759c35df7336c402bb8275ac9683"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "50fc3343ac490c6b08c0cf0d704e881d0d660be923fd3076db3e932007e726e3"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "516ea134e703e9fe26bcd1277a4b59ad30586ea90c365a87781d7887a646fe21"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "549e234ff32571b1f4076ac269fcce7a808d3bf98b76c8dd560e42dbc66d7d91"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "5d7f93296ee28f68447397bf5198428c9aeeab45705a55d53a6343455dcb2c3c"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "5ecfccd2329e37e9b7112a888e76d9feca2347f12f37918facbb893d7bb88ee8"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "6276eb85ef938dc035d59b87c8a7dc559a232f954962520137529d77b18ff1df"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "6b5063083824e5509fdba180721d55909ffacccc8adbec85268b48439423d78c"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "6eae65d4c3d33da080cff9c4ab1f711b15c1d9760809dad6ea763f3812d254cb"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "6f61efb26e76c45c4a227835ddeae96d83624fb0d29eb5df5b96e14ed1a0afb7"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "71e842ec9bc7abf543b47cf86b9a743baa95f4677d22baa4c7d5c69e49e9bc04"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "760f83faa07f8b64e9c33fc963d790a2edb24efb479e3520c14a45741cd9b2db"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "78a97cf6a8839a48c49271cdcbd5cf37ca2c1d6b7fdd86cc864f302b5e9bf459"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "7ce938a99998ed3c8aa7e7272dca1a610401ede816d36d0693907d863b10d9ea"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "8a6e050cb6164d3f830453754094c086ff2d0b2f3a897a1d9820f6139a1f0914"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "9394673a9f4de09e28b5356e7fff97d778f8abad85c9d5ac4a4b7e25a0de7717"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "94cd0549accc38d1494e1f8de71eca837d0509d0d44bf11d158524b0e12cebf9"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "a04bee9ab6a4da801eb9b51f1b708a1b5b5c9eb48c03f74198464c66f0d344ac"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "a23582810fedb8c0bc47524558fb6c56aac3fc252cb306072fd2815da2a47c32"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "a2c0cd47381a3229c403062f764160d57d4d175e022c1df84e168c6251a22eec"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "a9a3008438615669153eb86b26b61e09993921ebdd75385ddd748702c5adfddb"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "b02cf04496f6576afffef5ddd04a0cb7d49cf6be16a9059d793a30b035f6b6ac"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "b419ae593c86b87014b9be7396b385491ad7f320bde96826d0dd174459e54665"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "c0a7bb1a68a5d3471880e264621346c48665b3bf1c3759d682fc0864c540bd9e"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "c70cc23f12726be8f8bc72e41d5065d77e4515efae3690326764ea1b07845cfb"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "c8daeb2d2174beb4575b77482320303f3d39b8e81153da4f0fb08eb5fe86a6c5"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "cb3d760a6117f621261d662bccc8ef5bc32ca673e037c83fbe565324f5c46936"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "d55f3dffadd674514ad19451161118fd010988540cee43d8bc20675e775925de"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "d89c3468de4cdc4f08a57e214384d0471911a3830fcdaf7a8cc587e42a866372"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "db391fa7c66df6762ee3f00c95a89e6d428f4d60e7abc8328f4fe155b5ac6e54"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "dfb781ff7eaa91a6f7fd41776ec37c5853c795d3b358d4896fdbb5df168af422"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "e5bf0ed4490068a2e72ac03d786693adeb909981cc596425d09032d372bcc849"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "e7aec276d68421f9574040c26e2a7c3771060bc0cff408bae1dcb19d3ab1e63c"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "ef639cb3372f69ec44915fafcd6698b6cc78fbe0c2ea41be867f6ed612811963"}}, + {name = "cryptography-46.0.3-py3-none-any.whl", hashes = {sha256 = "f260d0d41e9b4da1ed1e0f1ce571f97fe370b152ab18778e9e8f67d6af432018"}}, +] + + +[[packages]] +name = "distlib" +version = "0.4.0" +marker = "dependency_groups in ('dev', 'test')" +wheels = [ + {name = "distlib-0.4.0-py3-none-any.whl", hashes = {sha256 = "9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16"}}, + {name = "distlib-0.4.0-py3-none-any.whl", hashes = {sha256 = "feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d"}}, +] + + +[[packages]] +name = "docutils" +version = "0.21.2" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.9')" +wheels = [ + {name = "docutils-0.21.2-py3-none-any.whl", hashes = {sha256 = "3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"}}, + {name = "docutils-0.21.2-py3-none-any.whl", hashes = {sha256 = "dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"}}, +] + + +[[packages]] +name = "exceptiongroup" +version = "1.1.0" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.7')" +wheels = [ + {name = "exceptiongroup-1.1.0-py3-none-any.whl", hashes = {sha256 = "327cbda3da756e2de031a3107b81ab7b3770a602c4d16ca618298c526f4bec1e"}}, + {name = "exceptiongroup-1.1.0-py3-none-any.whl", hashes = {sha256 = "bcb67d800a4497e1b404c2dd44fca47d3b7a5e5433dbab67f96c1a685cdfdf23"}}, +] + + +[[packages]] +name = "execnet" +version = "2.1.2" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.8')" +wheels = [ + {name = "execnet-2.1.2-py3-none-any.whl", hashes = {sha256 = "63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd"}}, + {name = "execnet-2.1.2-py3-none-any.whl", hashes = {sha256 = "67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec"}}, +] + + +[[packages]] +name = "filelock" +version = "3.20.0" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.10')" +wheels = [ + {name = "filelock-3.20.0-py3-none-any.whl", hashes = {sha256 = "339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2"}}, + {name = "filelock-3.20.0-py3-none-any.whl", hashes = {sha256 = "711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4"}}, +] + + +[[packages]] +name = "flake8" +version = "3.9.2" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4')" +wheels = [ + {name = "flake8-3.9.2-py3-none-any.whl", hashes = {sha256 = "07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}}, + {name = "flake8-3.9.2-py3-none-any.whl", hashes = {sha256 = "bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}}, +] + + +[[packages]] +name = "flaky" +version = "3.8.1" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.5')" +wheels = [ + {name = "flaky-3.8.1-py3-none-any.whl", hashes = {sha256 = "194ccf4f0d3a22b2de7130f4b62e45e977ac1b5ccad74d4d48f3005dcc38815e"}}, + {name = "flaky-3.8.1-py3-none-any.whl", hashes = {sha256 = "47204a81ec905f3d5acfbd61daeabcada8f9d4031616d9bcb0618461729699f5"}}, +] + + +[[packages]] +name = "gunicorn" +version = "23.0.0" +marker = "dependency_groups in ('dev', 'test') and (sys_platform == 'linux')" +wheels = [ + {name = "gunicorn-23.0.0-py3-none-any.whl", hashes = {sha256 = "ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d"}}, + {name = "gunicorn-23.0.0-py3-none-any.whl", hashes = {sha256 = "f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec"}}, +] + + +[[packages]] +name = "id" +version = "1.5.0" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.8')" +wheels = [ + {name = "id-1.5.0-py3-none-any.whl", hashes = {sha256 = "292cb8a49eacbbdbce97244f47a97b4c62540169c976552e497fd57df0734c1d"}}, + {name = "id-1.5.0-py3-none-any.whl", hashes = {sha256 = "f1434e1cef91f2cbb8a4ec64663d5a23b9ed43ef44c4c957d02583d61714c658"}}, +] + + +[[packages]] +name = "identify" +version = "2.6.15" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.9')" +wheels = [ + {name = "identify-2.6.15-py3-none-any.whl", hashes = {sha256 = "1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757"}}, + {name = "identify-2.6.15-py3-none-any.whl", hashes = {sha256 = "e4f4864b96c6557ef2a1e1c951771838f4edc9df3a72ec7118b338801b11c7bf"}}, +] + + +[[packages]] +name = "idna" +version = "3.11" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.8')" +wheels = [ + {name = "idna-3.11-py3-none-any.whl", hashes = {sha256 = "771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea"}}, + {name = "idna-3.11-py3-none-any.whl", hashes = {sha256 = "795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902"}}, +] + + +[[packages]] +name = "imagesize" +version = "1.4.1" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3')" +wheels = [ + {name = "imagesize-1.4.1-py3-none-any.whl", hashes = {sha256 = "0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}}, + {name = "imagesize-1.4.1-py3-none-any.whl", hashes = {sha256 = "69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}}, +] + + +[[packages]] +name = "importlib-metadata" +version = "8.7.0" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.9')" +wheels = [ + {name = "importlib-metadata-8.7.0-py3-none-any.whl", hashes = {sha256 = "d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000"}}, + {name = "importlib-metadata-8.7.0-py3-none-any.whl", hashes = {sha256 = "e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd"}}, +] + + +[[packages]] +name = "importlib-resources" +version = "6.5.2" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.9')" +wheels = [ + {name = "importlib-resources-6.5.2-py3-none-any.whl", hashes = {sha256 = "185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c"}}, + {name = "importlib-resources-6.5.2-py3-none-any.whl", hashes = {sha256 = "789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec"}}, +] + + +[[packages]] +name = "iniconfig" +version = "2.3.0" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.10')" +wheels = [ + {name = "iniconfig-2.3.0-py3-none-any.whl", hashes = {sha256 = "c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}}, + {name = "iniconfig-2.3.0-py3-none-any.whl", hashes = {sha256 = "f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}}, +] + + +[[packages]] +name = "invoke" +version = "2.2.1" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.6')" +wheels = [ + {name = "invoke-2.2.1-py3-none-any.whl", hashes = {sha256 = "2413bc441b376e5cd3f55bb5d364f973ad8bdd7bf87e53c79de3c11bf3feecc8"}}, + {name = "invoke-2.2.1-py3-none-any.whl", hashes = {sha256 = "515bf49b4a48932b79b024590348da22f39c4942dff991ad1fb8b8baea1be707"}}, +] + + +[[packages]] +name = "jaraco.classes" +version = "3.4.0" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.8')" +wheels = [ + {name = "jaraco.classes-3.4.0-py3-none-any.whl", hashes = {sha256 = "47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd"}}, + {name = "jaraco.classes-3.4.0-py3-none-any.whl", hashes = {sha256 = "f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790"}}, +] + + +[[packages]] +name = "jaraco.context" +version = "6.0.1" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.8')" +wheels = [ + {name = "jaraco.context-6.0.1-py3-none-any.whl", hashes = {sha256 = "9bae4ea555cf0b14938dc0aee7c9f32ed303aa20a3b73e7dc80111628792d1b3"}}, + {name = "jaraco.context-6.0.1-py3-none-any.whl", hashes = {sha256 = "f797fc481b490edb305122c9181830a3a5b76d84ef6d1aef2fb9b47ab956f9e4"}}, +] + + +[[packages]] +name = "jaraco.functools" +version = "4.3.0" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.9')" +wheels = [ + {name = "jaraco.functools-4.3.0-py3-none-any.whl", hashes = {sha256 = "227ff8ed6f7b8f62c56deff101545fa7543cf2c8e7b82a7c2116e672f29c26e8"}}, + {name = "jaraco.functools-4.3.0-py3-none-any.whl", hashes = {sha256 = "cfd13ad0dd2c47a3600b439ef72d8615d482cedcff1632930d6f28924d92f294"}}, +] + + +[[packages]] +name = "jeepney" +version = "0.9.0" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.7')" +wheels = [ + {name = "jeepney-0.9.0-py3-none-any.whl", hashes = {sha256 = "97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683"}}, + {name = "jeepney-0.9.0-py3-none-any.whl", hashes = {sha256 = "cf0e9e845622b81e4a28df94c40345400256ec608d0e55bb8a3feaa9163f5732"}}, +] + + +[[packages]] +name = "jinja2" +version = "3.1.6" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.7')" +wheels = [ + {name = "jinja2-3.1.6-py3-none-any.whl", hashes = {sha256 = "0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}}, + {name = "jinja2-3.1.6-py3-none-any.whl", hashes = {sha256 = "85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}}, +] + + +[[packages]] +name = "keyring" +version = "25.7.0" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.9')" +wheels = [ + {name = "keyring-25.7.0-py3-none-any.whl", hashes = {sha256 = "be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f"}}, + {name = "keyring-25.7.0-py3-none-any.whl", hashes = {sha256 = "fe01bd85eb3f8fb3dd0405defdeac9a5b4f6f0439edbb3149577f244a2e8245b"}}, +] + + +[[packages]] +name = "legacy-cgi" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.13')" + + +[[packages]] +name = "linkify-it-py" +version = "2.0.3" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.7')" +wheels = [ + {name = "linkify-it-py-2.0.3-py3-none-any.whl", hashes = {sha256 = "68cda27e162e9215c17d786649d1da0021a451bdc436ef9e0fa0ba5234b9b048"}}, + {name = "linkify-it-py-2.0.3-py3-none-any.whl", hashes = {sha256 = "6bcbc417b0ac14323382aef5c5192c0075bf8a9d6b41820a2b66371eac6b6d79"}}, +] + + +[[packages]] +name = "markdown-it-py" +version = "3.0.0" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.8')" +wheels = [ + {name = "markdown-it-py-3.0.0-py3-none-any.whl", hashes = {sha256 = "355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}}, + {name = "markdown-it-py-3.0.0-py3-none-any.whl", hashes = {sha256 = "e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}}, +] + + +[[packages]] +name = "markupsafe" +version = "3.0.3" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.9')" +wheels = [ + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a"}}, + {name = "markupsafe-3.0.3-py3-none-any.whl", hashes = {sha256 = "fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50"}}, +] + + +[[packages]] +name = "mccabe" +version = "0.6.1" +marker = "dependency_groups in ('dev', 'test')" +wheels = [ + {name = "mccabe-0.6.1-py3-none-any.whl", hashes = {sha256 = "ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}}, + {name = "mccabe-0.6.1-py3-none-any.whl", hashes = {sha256 = "dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}}, +] + + +[[packages]] +name = "mdit-py-plugins" +version = "0.5.0" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.10')" +wheels = [ + {name = "mdit-py-plugins-0.5.0-py3-none-any.whl", hashes = {sha256 = "07a08422fc1936a5d26d146759e9155ea466e842f5ab2f7d2266dd084c8dab1f"}}, + {name = "mdit-py-plugins-0.5.0-py3-none-any.whl", hashes = {sha256 = "f4918cb50119f50446560513a8e311d574ff6aaed72606ddae6d35716fe809c6"}}, +] + + +[[packages]] +name = "mdurl" +version = "0.1.2" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.7')" +wheels = [ + {name = "mdurl-0.1.2-py3-none-any.whl", hashes = {sha256 = "84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}}, + {name = "mdurl-0.1.2-py3-none-any.whl", hashes = {sha256 = "bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}}, +] + + +[[packages]] +name = "mock" +version = "5.2.0" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.6')" +wheels = [ + {name = "mock-5.2.0-py3-none-any.whl", hashes = {sha256 = "4e460e818629b4b173f32d08bf30d3af8123afbb8e04bb5707a1fd4799e503f0"}}, + {name = "mock-5.2.0-py3-none-any.whl", hashes = {sha256 = "7ba87f72ca0e915175596069dbbcc7c75af7b5e9b9bc107ad6349ede0819982f"}}, +] + + +[[packages]] +name = "more-itertools" +version = "10.8.0" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.9')" +wheels = [ + {name = "more-itertools-10.8.0-py3-none-any.whl", hashes = {sha256 = "52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b"}}, + {name = "more-itertools-10.8.0-py3-none-any.whl", hashes = {sha256 = "f638ddf8a1a0d134181275fb5d58b086ead7c6a72429ad725c67503f13ba30bd"}}, +] + + +[[packages]] +name = "mypy-extensions" +version = "1.1.0" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.8')" +wheels = [ + {name = "mypy-extensions-1.1.0-py3-none-any.whl", hashes = {sha256 = "1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}}, + {name = "mypy-extensions-1.1.0-py3-none-any.whl", hashes = {sha256 = "52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}}, +] + + +[[packages]] +name = "myst-parser" +version = "4.0.1" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.10')" +wheels = [ + {name = "myst-parser-4.0.1-py3-none-any.whl", hashes = {sha256 = "5cfea715e4f3574138aecbf7d54132296bfd72bb614d31168f48c477a830a7c4"}}, + {name = "myst-parser-4.0.1-py3-none-any.whl", hashes = {sha256 = "9134e88959ec3b5780aedf8a99680ea242869d012e8821db3126d427edc9c95d"}}, +] + + +[[packages]] +name = "nh3" +version = "0.3.2" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.8')" +wheels = [ + {name = "nh3-0.3.2-py3-none-any.whl", hashes = {sha256 = "019ecbd007536b67fdf76fab411b648fb64e2257ca3262ec80c3425c24028c80"}}, + {name = "nh3-0.3.2-py3-none-any.whl", hashes = {sha256 = "03d617e5c8aa7331bd2659c654e021caf9bba704b109e7b2b28b039a00949fe5"}}, + {name = "nh3-0.3.2-py3-none-any.whl", hashes = {sha256 = "0dca4365db62b2d71ff1620ee4f800c4729849906c5dd504ee1a7b2389558e31"}}, + {name = "nh3-0.3.2-py3-none-any.whl", hashes = {sha256 = "0fe7ee035dd7b2290715baf29cb27167dddd2ff70ea7d052c958dbd80d323c99"}}, + {name = "nh3-0.3.2-py3-none-any.whl", hashes = {sha256 = "13398e676a14d6233f372c75f52d5ae74f98210172991f7a3142a736bd92b131"}}, + {name = "nh3-0.3.2-py3-none-any.whl", hashes = {sha256 = "169db03df90da63286e0560ea0efa9b6f3b59844a9735514a1d47e6bb2c8c61b"}}, + {name = "nh3-0.3.2-py3-none-any.whl", hashes = {sha256 = "1710f3901cd6440ca92494ba2eb6dc260f829fa8d9196b659fa10de825610ce0"}}, + {name = "nh3-0.3.2-py3-none-any.whl", hashes = {sha256 = "1f9ba555a797dbdcd844b89523f29cdc90973d8bd2e836ea6b962cf567cadd93"}}, + {name = "nh3-0.3.2-py3-none-any.whl", hashes = {sha256 = "2ab70e8c6c7d2ce953d2a58102eefa90c2d0a5ed7aa40c7e29a487bc5e613131"}}, + {name = "nh3-0.3.2-py3-none-any.whl", hashes = {sha256 = "2c9850041b77a9147d6bbd6dbbf13eeec7009eb60b44e83f07fcb2910075bf9b"}}, + {name = "nh3-0.3.2-py3-none-any.whl", hashes = {sha256 = "403c11563e50b915d0efdb622866d1d9e4506bce590ef7da57789bf71dd148b5"}}, + {name = "nh3-0.3.2-py3-none-any.whl", hashes = {sha256 = "45c953e57028c31d473d6b648552d9cab1efe20a42ad139d78e11d8f42a36130"}}, + {name = "nh3-0.3.2-py3-none-any.whl", hashes = {sha256 = "562da3dca7a17f9077593214a9781a94b8d76de4f158f8c895e62f09573945fe"}}, + {name = "nh3-0.3.2-py3-none-any.whl", hashes = {sha256 = "6d66f41672eb4060cf87c037f760bdbc6847852ca9ef8e9c5a5da18f090abf87"}}, + {name = "nh3-0.3.2-py3-none-any.whl", hashes = {sha256 = "7064ccf5ace75825bd7bf57859daaaf16ed28660c1c6b306b649a9eda4b54b1e"}}, + {name = "nh3-0.3.2-py3-none-any.whl", hashes = {sha256 = "72d67c25a84579f4a432c065e8b4274e53b7cf1df8f792cf846abfe2c3090866"}}, + {name = "nh3-0.3.2-py3-none-any.whl", hashes = {sha256 = "7bb18403f02b655a1bbe4e3a4696c2ae1d6ae8f5991f7cacb684b1ae27e6c9f7"}}, + {name = "nh3-0.3.2-py3-none-any.whl", hashes = {sha256 = "91e9b001101fb4500a2aafe3e7c92928d85242d38bf5ac0aba0b7480da0a4cd6"}}, + {name = "nh3-0.3.2-py3-none-any.whl", hashes = {sha256 = "a40202fd58e49129764f025bbaae77028e420f1d5b3c8e6f6fd3a6490d513868"}}, + {name = "nh3-0.3.2-py3-none-any.whl", hashes = {sha256 = "c8745454cdd28bbbc90861b80a0111a195b0e3961b9fa2e672be89eb199fa5d8"}}, + {name = "nh3-0.3.2-py3-none-any.whl", hashes = {sha256 = "cf5964d54edd405e68583114a7cba929468bcd7db5e676ae38ee954de1cfc104"}}, + {name = "nh3-0.3.2-py3-none-any.whl", hashes = {sha256 = "d18957a90806d943d141cc5e4a0fefa1d77cf0d7a156878bf9a66eed52c9cc7d"}}, + {name = "nh3-0.3.2-py3-none-any.whl", hashes = {sha256 = "dce4248edc427c9b79261f3e6e2b3ecbdd9b88c267012168b4a7b3fc6fd41d13"}}, + {name = "nh3-0.3.2-py3-none-any.whl", hashes = {sha256 = "f2f55c4d2d5a207e74eefe4d828067bbb01300e06e2a7436142f915c5928de07"}}, + {name = "nh3-0.3.2-py3-none-any.whl", hashes = {sha256 = "f394759a06df8b685a4ebfb1874fb67a9cbfd58c64fc5ed587a663c0e63ec376"}}, + {name = "nh3-0.3.2-py3-none-any.whl", hashes = {sha256 = "f97f8b25cb2681d25e2338148159447e4d689aafdccfcf19e61ff7db3905768a"}}, +] + + +[[packages]] +name = "nodeenv" +version = "1.9.1" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6')" +wheels = [ + {name = "nodeenv-1.9.1-py3-none-any.whl", hashes = {sha256 = "6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}}, + {name = "nodeenv-1.9.1-py3-none-any.whl", hashes = {sha256 = "ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}}, +] + + +[[packages]] +name = "packaging" +version = "25.0" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.8')" +wheels = [ + {name = "packaging-25.0-py3-none-any.whl", hashes = {sha256 = "29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}}, + {name = "packaging-25.0-py3-none-any.whl", hashes = {sha256 = "d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}}, +] + + +[[packages]] +name = "parse" +version = "1.20.2" +marker = "dependency_groups in ('dev', 'test')" +wheels = [ + {name = "parse-1.20.2-py3-none-any.whl", hashes = {sha256 = "967095588cb802add9177d0c0b6133b5ba33b1ea9007ca800e526f42a85af558"}}, + {name = "parse-1.20.2-py3-none-any.whl", hashes = {sha256 = "b41d604d16503c79d81af5165155c0b20f6c8d6c559efa66b4b695c3e5a0a0ce"}}, +] + + +[[packages]] +name = "parver" +version = "0.5" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.8')" +wheels = [ + {name = "parver-0.5-py3-none-any.whl", hashes = {sha256 = "2281b187276c8e8e3c15634f62287b2fb6fe0efe3010f739a6bd1e45fa2bf2b2"}}, + {name = "parver-0.5-py3-none-any.whl", hashes = {sha256 = "b9fde1e6bb9ce9f07e08e9c4bea8d8825c5e78e18a0052d02e02bf9517eb4777"}}, +] + + +[[packages]] +name = "pathspec" +version = "0.12.1" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.8')" +wheels = [ + {name = "pathspec-0.12.1-py3-none-any.whl", hashes = {sha256 = "a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}}, + {name = "pathspec-0.12.1-py3-none-any.whl", hashes = {sha256 = "a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}}, +] + + +[[packages]] +name = "pip" +version = "25.3" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.9')" +wheels = [ + {name = "pip-25.3-py3-none-any.whl", hashes = {sha256 = "8d0538dbbd7babbd207f261ed969c65de439f6bc9e5dbd3b3b9a77f25d95f343"}}, + {name = "pip-25.3-py3-none-any.whl", hashes = {sha256 = "9655943313a94722b7774661c21049070f6bbb0a1516bf02f7c8d5d9201514cd"}}, +] + + +[[packages]] +name = "pipenv" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.10')" + + +[[packages]] +name = "platformdirs" +version = "4.5.1" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.10')" +wheels = [ + {name = "platformdirs-4.5.1-py3-none-any.whl", hashes = {sha256 = "61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda"}}, + {name = "platformdirs-4.5.1-py3-none-any.whl", hashes = {sha256 = "d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31"}}, +] + + +[[packages]] +name = "pluggy" +version = "1.6.0" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.9')" +wheels = [ + {name = "pluggy-1.6.0-py3-none-any.whl", hashes = {sha256 = "7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}}, + {name = "pluggy-1.6.0-py3-none-any.whl", hashes = {sha256 = "e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}}, +] + + +[[packages]] +name = "pre-commit" +version = "2.21.0" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.7')" +wheels = [ + {name = "pre-commit-2.21.0-py3-none-any.whl", hashes = {sha256 = "31ef31af7e474a8d8995027fefdfcf509b5c913ff31f2015b4ec4beb26a6f658"}}, + {name = "pre-commit-2.21.0-py3-none-any.whl", hashes = {sha256 = "e2f91727039fc39a92f58a588a25b87f936de6567eed4f0e673e0507edc75bad"}}, +] + + +[[packages]] +name = "pycodestyle" +version = "2.7.0" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3')" +wheels = [ + {name = "pycodestyle-2.7.0-py3-none-any.whl", hashes = {sha256 = "514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}}, + {name = "pycodestyle-2.7.0-py3-none-any.whl", hashes = {sha256 = "c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}}, +] + + +[[packages]] +name = "pycparser" +version = "2.23" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.8')" +wheels = [ + {name = "pycparser-2.23-py3-none-any.whl", hashes = {sha256 = "78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2"}}, + {name = "pycparser-2.23-py3-none-any.whl", hashes = {sha256 = "e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934"}}, +] + + +[[packages]] +name = "pyenchant" +version = "3.3.0" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.9')" +wheels = [ + {name = "pyenchant-3.3.0-py3-none-any.whl", hashes = {sha256 = "04a5bd0e022ebe2e8c6d9e498ec3d650602e264ec5486e9c6a1b7f99c9507c49"}}, + {name = "pyenchant-3.3.0-py3-none-any.whl", hashes = {sha256 = "1d55e075645a6edbb3c590fb42f9e02b4d455e4affe28a2227d5cb6d4868e626"}}, + {name = "pyenchant-3.3.0-py3-none-any.whl", hashes = {sha256 = "3da00b1d01314d85aac733bb997415d7a3e875666dc81735ddcf320aa36b7a70"}}, + {name = "pyenchant-3.3.0-py3-none-any.whl", hashes = {sha256 = "825288246b5debc9436f91967650974ef0d5636458502619e322c476f1283891"}}, +] + + +[[packages]] +name = "pyflakes" +version = "2.3.1" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3')" +wheels = [ + {name = "pyflakes-2.3.1-py3-none-any.whl", hashes = {sha256 = "7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}}, + {name = "pyflakes-2.3.1-py3-none-any.whl", hashes = {sha256 = "f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}}, +] + + +[[packages]] +name = "pygments" +version = "2.19.2" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.8')" +wheels = [ + {name = "pygments-2.19.2-py3-none-any.whl", hashes = {sha256 = "636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}}, + {name = "pygments-2.19.2-py3-none-any.whl", hashes = {sha256 = "86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}}, +] + + +[[packages]] +name = "pypiserver" +version = "2.4.0" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.8')" +wheels = [ + {name = "pypiserver-2.4.0-py3-none-any.whl", hashes = {sha256 = "0dc0e8300c12c855253b36b34beebfc28305c2c9e39151540a2886b641d4b456"}}, + {name = "pypiserver-2.4.0-py3-none-any.whl", hashes = {sha256 = "43700f711ce092e148e2bb6e7be22a684af090344bf5c166f8ec14eb7bba24bc"}}, +] + + +[[packages]] +name = "pyproject-hooks" +version = "1.2.0" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.7')" +wheels = [ + {name = "pyproject-hooks-1.2.0-py3-none-any.whl", hashes = {sha256 = "1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8"}}, + {name = "pyproject-hooks-1.2.0-py3-none-any.whl", hashes = {sha256 = "9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913"}}, +] + + +[[packages]] +name = "pytest" +version = "9.0.2" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.10')" +wheels = [ + {name = "pytest-9.0.2-py3-none-any.whl", hashes = {sha256 = "711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b"}}, + {name = "pytest-9.0.2-py3-none-any.whl", hashes = {sha256 = "75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11"}}, +] + + +[[packages]] +name = "pytest-cov" +version = "4.1.0" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.7')" +wheels = [ + {name = "pytest-cov-4.1.0-py3-none-any.whl", hashes = {sha256 = "3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}}, + {name = "pytest-cov-4.1.0-py3-none-any.whl", hashes = {sha256 = "6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}}, +] + + +[[packages]] +name = "pytest-timeout" +version = "2.4.0" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.7')" +wheels = [ + {name = "pytest-timeout-2.4.0-py3-none-any.whl", hashes = {sha256 = "7e68e90b01f9eff71332b25001f85c75495fc4e3a836701876183c4bcfd0540a"}}, + {name = "pytest-timeout-2.4.0-py3-none-any.whl", hashes = {sha256 = "c42667e5cdadb151aeb5b26d114aff6bdf5a907f176a007a30b940d3d865b5c2"}}, +] + + +[[packages]] +name = "pytest-xdist" +version = "3.8.0" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.9')" +wheels = [ + {name = "pytest-xdist-3.8.0-py3-none-any.whl", hashes = {sha256 = "202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88"}}, + {name = "pytest-xdist-3.8.0-py3-none-any.whl", hashes = {sha256 = "7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1"}}, +] + + +[[packages]] +name = "pyyaml" +version = "6.0.1" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.6')" +wheels = [ + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}}, + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}}, + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}}, + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}}, + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}}, + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}}, + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}}, + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}}, + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}}, + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}}, + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}}, + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}}, + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}}, + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}}, + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}}, + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}}, + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}}, + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}}, + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}}, + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}}, + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}}, + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}}, + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}}, + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}}, + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}}, + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}}, + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}}, + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}}, + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}}, + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}}, + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}}, + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}}, + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}}, + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}}, + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}}, + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}}, + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}}, + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}}, + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}}, + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}}, + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}}, + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}}, + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}}, + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}}, + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}}, + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}}, + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}}, + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}}, + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}}, + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}}, + {name = "pyyaml-6.0.1-py3-none-any.whl", hashes = {sha256 = "fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}}, +] + + +[[packages]] +name = "readme-renderer" +version = "44.0" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.9')" +wheels = [ + {name = "readme-renderer-44.0-py3-none-any.whl", hashes = {sha256 = "2fbca89b81a08526aadf1357a8c2ae889ec05fb03f5da67f9769c9a592166151"}}, + {name = "readme-renderer-44.0-py3-none-any.whl", hashes = {sha256 = "8712034eabbfa6805cacf1402b4eeb2a73028f72d1166d6f5cb7f9c047c5d1e1"}}, +] + + +[[packages]] +name = "requests" +version = "2.32.5" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.9')" +wheels = [ + {name = "requests-2.32.5-py3-none-any.whl", hashes = {sha256 = "2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"}}, + {name = "requests-2.32.5-py3-none-any.whl", hashes = {sha256 = "dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"}}, +] + + +[[packages]] +name = "requests-toolbelt" +version = "1.0.0" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3')" +wheels = [ + {name = "requests-toolbelt-1.0.0-py3-none-any.whl", hashes = {sha256 = "7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}}, + {name = "requests-toolbelt-1.0.0-py3-none-any.whl", hashes = {sha256 = "cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}}, +] + + +[[packages]] +name = "rfc3986" +version = "2.0.0" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.7')" +wheels = [ + {name = "rfc3986-2.0.0-py3-none-any.whl", hashes = {sha256 = "50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd"}}, + {name = "rfc3986-2.0.0-py3-none-any.whl", hashes = {sha256 = "97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c"}}, +] + + +[[packages]] +name = "rich" +version = "14.2.0" +marker = "dependency_groups in ('dev', 'test') and (python_full_version >= '3.8.0')" +wheels = [ + {name = "rich-14.2.0-py3-none-any.whl", hashes = {sha256 = "73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4"}}, + {name = "rich-14.2.0-py3-none-any.whl", hashes = {sha256 = "76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd"}}, +] + + +[[packages]] +name = "secretstorage" +version = "3.5.0" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.10')" +wheels = [ + {name = "secretstorage-3.5.0-py3-none-any.whl", hashes = {sha256 = "0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137"}}, + {name = "secretstorage-3.5.0-py3-none-any.whl", hashes = {sha256 = "f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be"}}, +] + + +[[packages]] +name = "semver" +version = "3.0.4" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.7')" +wheels = [ + {name = "semver-3.0.4-py3-none-any.whl", hashes = {sha256 = "9c824d87ba7f7ab4a1890799cec8596f15c1241cb473404ea1cb0c55e4b04746"}}, + {name = "semver-3.0.4-py3-none-any.whl", hashes = {sha256 = "afc7d8c584a5ed0a11033af086e8af226a9c0b206f313e0301f8dd7b6b589602"}}, +] + + +[[packages]] +name = "setuptools" +version = "80.9.0" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.9')" +wheels = [ + {name = "setuptools-80.9.0-py3-none-any.whl", hashes = {sha256 = "062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922"}}, + {name = "setuptools-80.9.0-py3-none-any.whl", hashes = {sha256 = "f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c"}}, +] + + +[[packages]] +name = "snowballstemmer" +version = "3.0.1" +marker = "dependency_groups in ('dev', 'test') and (python_version not in '3.0, 3.1, 3.2')" +wheels = [ + {name = "snowballstemmer-3.0.1-py3-none-any.whl", hashes = {sha256 = "6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064"}}, + {name = "snowballstemmer-3.0.1-py3-none-any.whl", hashes = {sha256 = "6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895"}}, +] + + +[[packages]] +name = "soupsieve" +version = "2.8" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.9')" +wheels = [ + {name = "soupsieve-2.8-py3-none-any.whl", hashes = {sha256 = "0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c"}}, + {name = "soupsieve-2.8-py3-none-any.whl", hashes = {sha256 = "e2dd4a40a628cb5f28f6d4b0db8800b8f581b65bb380b97de22ba5ca8d72572f"}}, +] + + +[[packages]] +name = "sphinx" +version = "8.1.3" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.10')" +wheels = [ + {name = "sphinx-8.1.3-py3-none-any.whl", hashes = {sha256 = "09719015511837b76bf6e03e42eb7595ac8c2e41eeb9c29c5b755c6b677992a2"}}, + {name = "sphinx-8.1.3-py3-none-any.whl", hashes = {sha256 = "43c1911eecb0d3e161ad78611bc905d1ad0e523e4ddc202a58a821773dc4c927"}}, +] + + +[[packages]] +name = "sphinx-click" +version = "4.4.0" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.7')" +wheels = [ + {name = "sphinx-click-4.4.0-py3-none-any.whl", hashes = {sha256 = "2821c10a68fc9ee6ce7c92fad26540d8d8c8f45e6d7258f0e4fb7529ae8fab49"}}, + {name = "sphinx-click-4.4.0-py3-none-any.whl", hashes = {sha256 = "cc67692bd28f482c7f01531c61b64e9d2f069bfcf3d24cbbb51d4a84a749fa48"}}, +] + + +[[packages]] +name = "sphinxcontrib-applehelp" +version = "2.0.0" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.9')" +wheels = [ + {name = "sphinxcontrib-applehelp-2.0.0-py3-none-any.whl", hashes = {sha256 = "2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1"}}, + {name = "sphinxcontrib-applehelp-2.0.0-py3-none-any.whl", hashes = {sha256 = "4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5"}}, +] + + +[[packages]] +name = "sphinxcontrib-devhelp" +version = "2.0.0" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.9')" +wheels = [ + {name = "sphinxcontrib-devhelp-2.0.0-py3-none-any.whl", hashes = {sha256 = "411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad"}}, + {name = "sphinxcontrib-devhelp-2.0.0-py3-none-any.whl", hashes = {sha256 = "aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2"}}, +] + + +[[packages]] +name = "sphinxcontrib-htmlhelp" +version = "2.1.0" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.9')" +wheels = [ + {name = "sphinxcontrib-htmlhelp-2.1.0-py3-none-any.whl", hashes = {sha256 = "166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8"}}, + {name = "sphinxcontrib-htmlhelp-2.1.0-py3-none-any.whl", hashes = {sha256 = "c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9"}}, +] + + +[[packages]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.5')" +wheels = [ + {name = "sphinxcontrib-jsmath-1.0.1-py3-none-any.whl", hashes = {sha256 = "2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}}, + {name = "sphinxcontrib-jsmath-1.0.1-py3-none-any.whl", hashes = {sha256 = "a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}}, +] + + +[[packages]] +name = "sphinxcontrib-qthelp" +version = "2.0.0" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.9')" +wheels = [ + {name = "sphinxcontrib-qthelp-2.0.0-py3-none-any.whl", hashes = {sha256 = "4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab"}}, + {name = "sphinxcontrib-qthelp-2.0.0-py3-none-any.whl", hashes = {sha256 = "b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb"}}, +] + + +[[packages]] +name = "sphinxcontrib-serializinghtml" +version = "2.0.0" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.9')" +wheels = [ + {name = "sphinxcontrib-serializinghtml-2.0.0-py3-none-any.whl", hashes = {sha256 = "6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331"}}, + {name = "sphinxcontrib-serializinghtml-2.0.0-py3-none-any.whl", hashes = {sha256 = "e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d"}}, +] + + +[[packages]] +name = "sphinxcontrib-spelling" +version = "7.7.0" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.6')" +wheels = [ + {name = "sphinxcontrib-spelling-7.7.0-py3-none-any.whl", hashes = {sha256 = "56561c3f6a155b0946914e4de988729859315729dc181b5e4dc8a68fe78de35a"}}, + {name = "sphinxcontrib-spelling-7.7.0-py3-none-any.whl", hashes = {sha256 = "95a0defef8ffec6526f9e83b20cc24b08c9179298729d87976891840e3aa3064"}}, +] + + +[[packages]] +name = "stdeb" +version = "0.11.0" +marker = "dependency_groups in ('dev', 'test') and (sys_platform == 'linux')" +wheels = [ + {name = "stdeb-0.11.0-py3-none-any.whl", hashes = {sha256 = "3f883c522ecb76394514ea4d282eda7671c8bd0db0fd904f9774ba20e02035e6"}}, + {name = "stdeb-0.11.0-py3-none-any.whl", hashes = {sha256 = "e7084e24f1616ab599d3f390599363b39670905179c261d23cd8ddcc0ecb3b71"}}, +] + + +[[packages]] +name = "tomli" +version = "2.3.0" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.8')" +wheels = [ + {name = "tomli-2.3.0-py3-none-any.whl", hashes = {sha256 = "00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456"}}, + {name = "tomli-2.3.0-py3-none-any.whl", hashes = {sha256 = "0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845"}}, + {name = "tomli-2.3.0-py3-none-any.whl", hashes = {sha256 = "0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999"}}, + {name = "tomli-2.3.0-py3-none-any.whl", hashes = {sha256 = "0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0"}}, + {name = "tomli-2.3.0-py3-none-any.whl", hashes = {sha256 = "1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878"}}, + {name = "tomli-2.3.0-py3-none-any.whl", hashes = {sha256 = "4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf"}}, + {name = "tomli-2.3.0-py3-none-any.whl", hashes = {sha256 = "4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3"}}, + {name = "tomli-2.3.0-py3-none-any.whl", hashes = {sha256 = "4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be"}}, + {name = "tomli-2.3.0-py3-none-any.whl", hashes = {sha256 = "4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52"}}, + {name = "tomli-2.3.0-py3-none-any.whl", hashes = {sha256 = "5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b"}}, + {name = "tomli-2.3.0-py3-none-any.whl", hashes = {sha256 = "5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67"}}, + {name = "tomli-2.3.0-py3-none-any.whl", hashes = {sha256 = "64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549"}}, + {name = "tomli-2.3.0-py3-none-any.whl", hashes = {sha256 = "70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba"}}, + {name = "tomli-2.3.0-py3-none-any.whl", hashes = {sha256 = "73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22"}}, + {name = "tomli-2.3.0-py3-none-any.whl", hashes = {sha256 = "74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c"}}, + {name = "tomli-2.3.0-py3-none-any.whl", hashes = {sha256 = "792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f"}}, + {name = "tomli-2.3.0-py3-none-any.whl", hashes = {sha256 = "7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6"}}, + {name = "tomli-2.3.0-py3-none-any.whl", hashes = {sha256 = "883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba"}}, + {name = "tomli-2.3.0-py3-none-any.whl", hashes = {sha256 = "88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45"}}, + {name = "tomli-2.3.0-py3-none-any.whl", hashes = {sha256 = "8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f"}}, + {name = "tomli-2.3.0-py3-none-any.whl", hashes = {sha256 = "940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77"}}, + {name = "tomli-2.3.0-py3-none-any.whl", hashes = {sha256 = "97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606"}}, + {name = "tomli-2.3.0-py3-none-any.whl", hashes = {sha256 = "a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441"}}, + {name = "tomli-2.3.0-py3-none-any.whl", hashes = {sha256 = "a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0"}}, + {name = "tomli-2.3.0-py3-none-any.whl", hashes = {sha256 = "a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f"}}, + {name = "tomli-2.3.0-py3-none-any.whl", hashes = {sha256 = "a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530"}}, + {name = "tomli-2.3.0-py3-none-any.whl", hashes = {sha256 = "ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05"}}, + {name = "tomli-2.3.0-py3-none-any.whl", hashes = {sha256 = "b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8"}}, + {name = "tomli-2.3.0-py3-none-any.whl", hashes = {sha256 = "b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005"}}, + {name = "tomli-2.3.0-py3-none-any.whl", hashes = {sha256 = "b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879"}}, + {name = "tomli-2.3.0-py3-none-any.whl", hashes = {sha256 = "be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae"}}, + {name = "tomli-2.3.0-py3-none-any.whl", hashes = {sha256 = "c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc"}}, + {name = "tomli-2.3.0-py3-none-any.whl", hashes = {sha256 = "c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b"}}, + {name = "tomli-2.3.0-py3-none-any.whl", hashes = {sha256 = "c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b"}}, + {name = "tomli-2.3.0-py3-none-any.whl", hashes = {sha256 = "cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e"}}, + {name = "tomli-2.3.0-py3-none-any.whl", hashes = {sha256 = "d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf"}}, + {name = "tomli-2.3.0-py3-none-any.whl", hashes = {sha256 = "d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac"}}, + {name = "tomli-2.3.0-py3-none-any.whl", hashes = {sha256 = "e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8"}}, + {name = "tomli-2.3.0-py3-none-any.whl", hashes = {sha256 = "e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b"}}, + {name = "tomli-2.3.0-py3-none-any.whl", hashes = {sha256 = "f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf"}}, + {name = "tomli-2.3.0-py3-none-any.whl", hashes = {sha256 = "feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463"}}, + {name = "tomli-2.3.0-py3-none-any.whl", hashes = {sha256 = "ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876"}}, +] + + +[[packages]] +name = "towncrier" +version = "25.8.0" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.9')" +wheels = [ + {name = "towncrier-25.8.0-py3-none-any.whl", hashes = {sha256 = "b953d133d98f9aeae9084b56a3563fd2519dfc6ec33f61c9cd2c61ff243fb513"}}, + {name = "towncrier-25.8.0-py3-none-any.whl", hashes = {sha256 = "eef16d29f831ad57abb3ae32a0565739866219f1ebfbdd297d32894eb9940eb1"}}, +] + + +[[packages]] +name = "twine" +version = "6.2.0" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.9')" +wheels = [ + {name = "twine-6.2.0-py3-none-any.whl", hashes = {sha256 = "418ebf08ccda9a8caaebe414433b0ba5e25eb5e4a927667122fbe8f829f985d8"}}, + {name = "twine-6.2.0-py3-none-any.whl", hashes = {sha256 = "e5ed0d2fd70c9959770dce51c8f39c8945c574e18173a7b81802dab51b4b75cf"}}, +] + + +[[packages]] +name = "typing-extensions" +version = "4.15.0" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.9')" +wheels = [ + {name = "typing-extensions-4.15.0-py3-none-any.whl", hashes = {sha256 = "0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}}, + {name = "typing-extensions-4.15.0-py3-none-any.whl", hashes = {sha256 = "f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}}, +] + + +[[packages]] +name = "uc-micro-py" +version = "1.0.3" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.7')" +wheels = [ + {name = "uc-micro-py-1.0.3-py3-none-any.whl", hashes = {sha256 = "d321b92cff673ec58027c04015fcaa8bb1e005478643ff4a500882eaab88c48a"}}, + {name = "uc-micro-py-1.0.3-py3-none-any.whl", hashes = {sha256 = "db1dffff340817673d7b466ec86114a9dc0e9d4d9b5ba229d9d60e5c12600cd5"}}, +] + + +[[packages]] +name = "urllib3" +version = "2.6.1" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.9')" +wheels = [ + {name = "urllib3-2.6.1-py3-none-any.whl", hashes = {sha256 = "5379eb6e1aba4088bae84f8242960017ec8d8e3decf30480b3a1abdaa9671a3f"}}, + {name = "urllib3-2.6.1-py3-none-any.whl", hashes = {sha256 = "e67d06fe947c36a7ca39f4994b08d73922d40e6cca949907be05efa6fd75110b"}}, +] + + +[[packages]] +name = "virtualenv" +version = "20.35.4" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.8')" +wheels = [ + {name = "virtualenv-20.35.4-py3-none-any.whl", hashes = {sha256 = "643d3914d73d3eeb0c552cbb12d7e82adf0e504dbf86a3182f8771a153a1971c"}}, + {name = "virtualenv-20.35.4-py3-none-any.whl", hashes = {sha256 = "c21c9cede36c9753eeade68ba7d523529f228a403463376cf821eaae2b650f1b"}}, +] + + +[[packages]] +name = "waitress" +version = "3.0.2" +marker = "dependency_groups in ('dev', 'test') and (sys_platform == 'win32')" +wheels = [ + {name = "waitress-3.0.2-py3-none-any.whl", hashes = {sha256 = "682aaaf2af0c44ada4abfb70ded36393f0e307f4ab9456a215ce0020baefc31f"}}, + {name = "waitress-3.0.2-py3-none-any.whl", hashes = {sha256 = "c56d67fd6e87c2ee598b76abdd4e96cfad1f24cacdea5078d382b1f9d7b5ed2e"}}, +] + + +[[packages]] +name = "zipp" +version = "3.21.0" +marker = "dependency_groups in ('dev', 'test') and (python_version >= '3.9')" +wheels = [ + {name = "zipp-3.21.0-py3-none-any.whl", hashes = {sha256 = "2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"}}, + {name = "zipp-3.21.0-py3-none-any.whl", hashes = {sha256 = "ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"}}, +] + +[tool.pipenv] +generated_from = "Pipfile.lock" +generation_date = "2025-12-09T11:19:36.437635+00:00" diff --git a/tests/integration/test_pylock.py b/tests/integration/test_pylock.py new file mode 100644 index 0000000000..f78e12acf0 --- /dev/null +++ b/tests/integration/test_pylock.py @@ -0,0 +1,370 @@ +import os +import shutil +from pathlib import Path + +import pytest + +from pipenv.project import Project +from pipenv.utils.pylock import PylockFile, find_pylock_file + + +@pytest.fixture +def pylock_project(tmp_path): + """Create a temporary project with a pylock.toml file.""" + # Copy the example pylock.toml to the temporary directory + example_pylock = Path(__file__).parent.parent.parent / "examples" / "pylock.toml" + tmp_pylock = tmp_path / "pylock.toml" + + # Create a simple Pipfile + pipfile_content = """ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +requests = "*" + +[dev-packages] + +[requires] +python_version = "3.8" +""" + + with open(tmp_path / "Pipfile", "w") as f: + f.write(pipfile_content) + + shutil.copy(example_pylock, tmp_pylock) + + # Change to the temporary directory + old_cwd = os.getcwd() + os.chdir(tmp_path) + + try: + yield tmp_path + finally: + os.chdir(old_cwd) + + +def test_find_pylock_file(pylock_project): + """Test that find_pylock_file correctly finds the pylock.toml file.""" + pylock_path = find_pylock_file(pylock_project) + assert pylock_path is not None + assert pylock_path.name == "pylock.toml" + assert pylock_path.exists() + + +def test_pylock_file_loading(pylock_project): + """Test loading a pylock.toml file.""" + pylock_path = pylock_project / "pylock.toml" + pylock = PylockFile.from_path(pylock_path) + + assert pylock.lock_version == "1.0" + assert pylock.created_by == "pipenv" + assert pylock.requires_python == ">=3.8" + # Updated example now has 5 packages: requests, urllib3, certifi, pytest, cryptography + assert len(pylock.packages) == 5 + assert pylock.packages[0]["name"] == "requests" + assert pylock.packages[0]["version"] == "2.28.1" + # Check new index field + assert pylock.packages[0].get("index") == "https://pypi.org/simple/" + + +def test_project_pylock_integration(pylock_project): + """Test that Project class correctly detects and uses pylock.toml.""" + # Create a project instance + project = Project(chdir=False) + + # Check that pylock.toml is detected + assert project.pylock_exists + assert project.pylock_location is not None + assert Path(project.pylock_location).name == "pylock.toml" + + # Check that lockfile_content returns the converted pylock content + lockfile_content = project.lockfile_content + assert "_meta" in lockfile_content + assert "default" in lockfile_content + assert "requests" in lockfile_content["default"] + assert "urllib3" in lockfile_content["default"] + assert "certifi" in lockfile_content["default"] + # pytest should be in develop section due to dependency_groups marker + assert "develop" in lockfile_content + assert "pytest" in lockfile_content["develop"] + + # Check that the converted content has the correct format + requests_entry = lockfile_content["default"]["requests"] + assert requests_entry["version"] == "==2.28.1" + assert "hashes" in requests_entry + assert len(requests_entry["hashes"]) == 1 + assert requests_entry["hashes"][0].startswith("sha256:") + + +@pytest.fixture +def pylock_write_project(tmp_path): + """Create a temporary project with a Pipfile that has use_pylock enabled.""" + # Create a simple Pipfile with use_pylock enabled + pipfile_content = """ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +requests = "*" + +[dev-packages] + +[requires] +python_version = "3.8" + +[pipenv] +use_pylock = true +""" + + # Create a simple Pipfile.lock + lockfile_content = """ +{ + "_meta": { + "hash": { + "sha256": "b8c2e1580c53e383cfe4254c1f16560b855d984c674dc07bcce19a8b5b28c6b2" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.8" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "certifi": { + "hashes": [ + "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14" + ], + "version": "==2022.9.24" + }, + "charset-normalizer": { + "hashes": [ + "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f" + ], + "version": "==2.1.1" + }, + "idna": { + "hashes": [ + "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" + ], + "version": "==3.4" + }, + "requests": { + "hashes": [ + "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7" + ], + "index": "pypi", + "version": "==2.28.1" + }, + "urllib3": { + "hashes": [ + "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997" + ], + "version": "==1.26.12" + } + }, + "develop": {} +} +""" + + with open(tmp_path / "Pipfile", "w") as f: + f.write(pipfile_content) + + with open(tmp_path / "Pipfile.lock", "w") as f: + f.write(lockfile_content) + + # Change to the temporary directory + old_cwd = os.getcwd() + os.chdir(tmp_path) + + try: + yield tmp_path + finally: + os.chdir(old_cwd) + + +def test_write_pylock_file(pylock_write_project): + """Test that Project class correctly writes pylock.toml files.""" + # Create a project instance + project = Project(chdir=False) + + # Check that use_pylock is enabled + assert project.use_pylock is True + + # Check that pylock_output_path is correct + assert project.pylock_output_path == str(pylock_write_project / "pylock.toml") + + # Load the lockfile content + lockfile_content = project.lockfile_content + + # Write the lockfile (which should also write pylock.toml) + project.write_lockfile(lockfile_content) + + # Check that pylock.toml was created + pylock_path = pylock_write_project / "pylock.toml" + assert pylock_path.exists() + + # Load the pylock.toml file and verify its contents + pylock = PylockFile.from_path(pylock_path) + + # Check basic properties + assert pylock.lock_version == "1.0" + assert pylock.created_by == "pipenv" + + # Check that all packages are included + package_names = [p["name"] for p in pylock.packages] + assert "requests" in package_names + assert "urllib3" in package_names + assert "certifi" in package_names + assert "charset-normalizer" in package_names + assert "idna" in package_names + + # Check that the tool.pipenv section exists + assert "pipenv" in pylock.tool + assert "generated_from" in pylock.tool["pipenv"] + assert pylock.tool["pipenv"]["generated_from"] == "Pipfile.lock" + + +@pytest.fixture +def pylock_write_named_project(tmp_path): + """Create a temporary project with a Pipfile that has use_pylock and pylock_name enabled.""" + # Create a simple Pipfile with use_pylock and pylock_name enabled + pipfile_content = """ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +requests = "*" + +[dev-packages] + +[requires] +python_version = "3.8" + +[pipenv] +use_pylock = true +pylock_name = "dev" +""" + + # Create a simple Pipfile.lock (same as in pylock_write_project) + lockfile_content = """ +{ + "_meta": { + "hash": { + "sha256": "b8c2e1580c53e383cfe4254c1f16560b855d984c674dc07bcce19a8b5b28c6b2" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.8" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "certifi": { + "hashes": [ + "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14" + ], + "version": "==2022.9.24" + }, + "charset-normalizer": { + "hashes": [ + "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f" + ], + "version": "==2.1.1" + }, + "idna": { + "hashes": [ + "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" + ], + "version": "==3.4" + }, + "requests": { + "hashes": [ + "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7" + ], + "index": "pypi", + "version": "==2.28.1" + }, + "urllib3": { + "hashes": [ + "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997" + ], + "version": "==1.26.12" + } + }, + "develop": {} +} +""" + + with open(tmp_path / "Pipfile", "w") as f: + f.write(pipfile_content) + + with open(tmp_path / "Pipfile.lock", "w") as f: + f.write(lockfile_content) + + # Change to the temporary directory + old_cwd = os.getcwd() + os.chdir(tmp_path) + + try: + yield tmp_path + finally: + os.chdir(old_cwd) + + +def test_write_named_pylock_file(pylock_write_named_project): + """Test that Project class correctly writes named pylock.toml files.""" + # Create a project instance + project = Project(chdir=False) + + # Check that use_pylock is enabled + assert project.use_pylock is True + + # Check that pylock_name is set + assert project.settings.get("pylock_name") == "dev" + + # Check that pylock_output_path is correct + assert project.pylock_output_path == str(pylock_write_named_project / "pylock.dev.toml") + + # Load the lockfile content + lockfile_content = project.lockfile_content + + # Write the lockfile (which should also write pylock.dev.toml) + project.write_lockfile(lockfile_content) + + # Check that pylock.dev.toml was created + pylock_path = pylock_write_named_project / "pylock.dev.toml" + assert pylock_path.exists() + + # Load the pylock.dev.toml file and verify its contents + pylock = PylockFile.from_path(pylock_path) + + # Check basic properties + assert pylock.lock_version == "1.0" + assert pylock.created_by == "pipenv" + + # Check that all packages are included + package_names = [p["name"] for p in pylock.packages] + assert "requests" in package_names + assert "urllib3" in package_names + assert "certifi" in package_names + assert "charset-normalizer" in package_names + assert "idna" in package_names diff --git a/tests/unit/test_pylock.py b/tests/unit/test_pylock.py new file mode 100644 index 0000000000..d3399fab2c --- /dev/null +++ b/tests/unit/test_pylock.py @@ -0,0 +1,581 @@ +import os +import tempfile +from pathlib import Path + +import pytest + +from pipenv.utils.pylock import PylockFile, PylockFormatError, PylockVersionError, find_pylock_file + + +@pytest.fixture +def valid_pylock_content(): + return """ +lock-version = '1.0' +environments = ["sys_platform == 'win32'", "sys_platform == 'linux'"] +requires-python = '==3.12' +created-by = 'test-tool' + +[[packages]] +name = 'requests' +version = '2.28.1' +requires-python = '>=3.7' + +[[packages.wheels]] +name = 'requests-2.28.1-py3-none-any.whl' +upload-time = '2022-07-13T14:00:00Z' +url = 'https://files.pythonhosted.org/packages/example/requests-2.28.1-py3-none-any.whl' +size = 61000 +hashes = {sha256 = 'abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890'} + +[[packages]] +name = 'pytest' +version = '7.0.0' +marker = "'dev' in dependency_groups or 'test' in dependency_groups" +requires-python = '>=3.6' + +[[packages.wheels]] +name = 'pytest-7.0.0-py3-none-any.whl' +upload-time = '2022-02-03T12:00:00Z' +url = 'https://files.pythonhosted.org/packages/example/pytest-7.0.0-py3-none-any.whl' +size = 45000 +hashes = {sha256 = '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'} +""" + + +@pytest.fixture +def invalid_version_pylock_content(): + return """ +lock-version = '2.0' +created-by = 'test-tool' + +[[packages]] +name = 'requests' +version = '2.28.1' +""" + + +@pytest.fixture +def missing_version_pylock_content(): + return """ +created-by = 'test-tool' + +[[packages]] +name = 'requests' +version = '2.28.1' +""" + + +@pytest.fixture +def pylock_file(valid_pylock_content): + with tempfile.NamedTemporaryFile(mode='w+', suffix='.toml', delete=False) as f: + f.write(valid_pylock_content) + f.flush() + path = f.name + + yield path + + # Clean up + if os.path.exists(path): + os.unlink(path) + + +def test_pylock_file_from_path(pylock_file): + """Test loading a pylock file from a path.""" + pylock = PylockFile.from_path(pylock_file) + + assert pylock.lock_version == "1.0" + assert pylock.created_by == "test-tool" + assert pylock.requires_python == "==3.12" + assert len(pylock.packages) == 2 + assert pylock.packages[0]["name"] == "requests" + assert pylock.packages[0]["version"] == "2.28.1" + assert pylock.packages[1]["name"] == "pytest" + assert pylock.packages[1]["marker"] == "'dev' in dependency_groups or 'test' in dependency_groups" + + +def test_pylock_file_invalid_version(invalid_version_pylock_content): + """Test loading a pylock file with an invalid version.""" + with tempfile.NamedTemporaryFile(mode='w+', suffix='.toml', delete=False) as f: + f.write(invalid_version_pylock_content) + f.flush() + path = f.name + + try: + with pytest.raises(PylockVersionError): + PylockFile.from_path(path) + finally: + if os.path.exists(path): + os.unlink(path) + + +def test_pylock_file_missing_version(missing_version_pylock_content): + """Test loading a pylock file with a missing version.""" + with tempfile.NamedTemporaryFile(mode='w+', suffix='.toml', delete=False) as f: + f.write(missing_version_pylock_content) + f.flush() + path = f.name + + try: + with pytest.raises(PylockFormatError): + PylockFile.from_path(path) + finally: + if os.path.exists(path): + os.unlink(path) + + +def test_find_pylock_file(): + """Test finding a pylock file.""" + with tempfile.TemporaryDirectory() as tmpdir: + # No pylock file + assert find_pylock_file(tmpdir) is None + + # Create pylock.toml + pylock_path = os.path.join(tmpdir, "pylock.toml") + with open(pylock_path, "w") as f: + f.write("lock-version = '1.0'") + + assert find_pylock_file(tmpdir) == Path(pylock_path) + + # Create pylock.dev.toml + os.unlink(pylock_path) # Remove pylock.toml + pylock_dev_path = os.path.join(tmpdir, "pylock.dev.toml") + with open(pylock_dev_path, "w") as f: + f.write("lock-version = '1.0'") + + assert find_pylock_file(tmpdir) == Path(pylock_dev_path) + + +def test_convert_to_pipenv_lockfile(pylock_file): + """Test converting a pylock file to a Pipfile.lock format.""" + pylock = PylockFile.from_path(pylock_file) + lockfile = pylock.convert_to_pipenv_lockfile() + + # Check structure + assert "_meta" in lockfile + assert "default" in lockfile + assert "develop" in lockfile + + # Check packages + assert "requests" in lockfile["default"] + assert "pytest" in lockfile["develop"] + + # Check package details + assert lockfile["default"]["requests"]["version"] == "==2.28.1" + assert "hashes" in lockfile["default"]["requests"] + assert lockfile["develop"]["pytest"]["version"] == "==7.0.0" + assert lockfile["develop"]["pytest"]["markers"] == "'dev' in dependency_groups or 'test' in dependency_groups" + + +def test_from_lockfile(tmp_path): + """Test creating a PylockFile from a Pipfile.lock file.""" + # Create a simple Pipfile.lock + lockfile_content = """ +{ + "_meta": { + "hash": { + "sha256": "b8c2e1580c53e383cfe4254c1f16560b855d984c674dc07bcce19a8b5b28c6b2" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.8" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "requests": { + "hashes": [ + "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7" + ], + "index": "pypi", + "version": "==2.28.1" + } + }, + "develop": { + "pytest": { + "hashes": [ + "sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" + ], + "version": "==7.0.0" + } + } +} +""" + lockfile_path = tmp_path / "Pipfile.lock" + pylock_path = tmp_path / "pylock.toml" + + with open(lockfile_path, "w") as f: + f.write(lockfile_content) + + # Create a PylockFile from the Pipfile.lock + pylock = PylockFile.from_lockfile(lockfile_path, pylock_path) + + # Check basic properties + assert pylock.lock_version == "1.0" + assert pylock.created_by == "pipenv" + assert pylock.requires_python == ">=3.8" + + # Check that packages were correctly converted + package_names = [p["name"] for p in pylock.packages] + assert "requests" in package_names + assert "pytest" in package_names + + # Check that the tool.pipenv section exists + assert "pipenv" in pylock.tool + assert "generated_from" in pylock.tool["pipenv"] + assert pylock.tool["pipenv"]["generated_from"] == "Pipfile.lock" + + +def test_wildcard_version_handling(tmp_path): + """Test that wildcard versions are handled correctly. + + When converting from Pipfile.lock to pylock.toml, wildcard versions should be skipped. + When converting back, packages without versions should get wildcard version. + """ + # Create a Pipfile.lock with a wildcard version + lockfile_content = """ +{ + "_meta": { + "hash": {"sha256": "test"}, + "pipfile-spec": 6, + "requires": {"python_version": "3.10"}, + "sources": [] + }, + "default": { + "legacy-cgi": { + "markers": "python_version >= '3.13'", + "version": "*" + }, + "requests": { + "version": "==2.28.1" + } + }, + "develop": {} +} +""" + lockfile_path = tmp_path / "Pipfile.lock" + pylock_path = tmp_path / "pylock.toml" + + with open(lockfile_path, "w") as f: + f.write(lockfile_content) + + # Create a PylockFile from the Pipfile.lock + pylock = PylockFile.from_lockfile(lockfile_path, pylock_path) + + # Check that legacy-cgi has no version (wildcard was skipped) + legacy_cgi_pkg = next((p for p in pylock.packages if p["name"] == "legacy-cgi"), None) + assert legacy_cgi_pkg is not None + assert "version" not in legacy_cgi_pkg # Wildcard version should not be stored + + # Check that requests has a version + requests_pkg = next((p for p in pylock.packages if p["name"] == "requests"), None) + assert requests_pkg is not None + assert requests_pkg["version"] == "2.28.1" + + # Now write and reload the pylock.toml + pylock.write() + loaded_pylock = PylockFile.from_path(pylock_path) + + # Convert back to Pipfile.lock format + converted_lockfile = loaded_pylock.convert_to_pipenv_lockfile() + + # Check that legacy-cgi gets wildcard version back + assert "legacy-cgi" in converted_lockfile["default"] + assert converted_lockfile["default"]["legacy-cgi"]["version"] == "*" + + # Check that requests keeps its pinned version + assert "requests" in converted_lockfile["default"] + assert converted_lockfile["default"]["requests"]["version"] == "==2.28.1" + + +def test_write_method(tmp_path): + """Test writing a PylockFile to disk.""" + # Create a simple PylockFile + pylock_data = { + "lock-version": "1.0", + "environments": ["sys_platform == 'linux'"], + "requires-python": ">=3.8", + "extras": [], + "dependency-groups": [], + "default-groups": [], + "created-by": "test", + "packages": [ + { + "name": "requests", + "version": "2.28.1", + "wheels": [ + { + "name": "requests-2.28.1-py3-none-any.whl", + "hashes": {"sha256": "test-hash"} + } + ] + } + ], + "tool": { + "pipenv": { + "generated_from": "test" + } + } + } + + pylock_path = tmp_path / "pylock.toml" + pylock = PylockFile(path=pylock_path, data=pylock_data) + + # Write the file + pylock.write() + + # Check that the file was created + assert pylock_path.exists() + + # Load the file and check its contents + loaded_pylock = PylockFile.from_path(pylock_path) + + # Check basic properties + assert loaded_pylock.lock_version == "1.0" + assert loaded_pylock.created_by == "test" + assert loaded_pylock.requires_python == ">=3.8" + + # Check that packages were correctly written + assert len(loaded_pylock.packages) == 1 + assert loaded_pylock.packages[0]["name"] == "requests" + assert loaded_pylock.packages[0]["version"] == "2.28.1" + + # Check that the tool.pipenv section was correctly written + assert "pipenv" in loaded_pylock.tool + assert loaded_pylock.tool["pipenv"]["generated_from"] == "test" + + +def test_get_packages_for_environment_marker_evaluation(tmp_path): + """Test that get_packages_for_environment correctly evaluates markers. + + This test verifies that: + - Packages without markers are always included + - Packages with dependency_groups markers are filtered based on provided groups + - Packages with extras markers are filtered based on provided extras + + Note: PEP 751 marker syntax uses 'value' in marker_variable, e.g.: + - 'dev' in dependency_groups + - 'crypto' in extras + """ + # Create a pylock file with various markers using PEP 751 syntax + pylock_content = """ +lock-version = '1.0' +created-by = 'test-tool' + +[[packages]] +name = 'requests' +version = '2.28.1' + +[[packages]] +name = 'pytest' +version = '7.0.0' +marker = "'dev' in dependency_groups or 'test' in dependency_groups" + +[[packages]] +name = 'sphinx' +version = '6.0.0' +marker = "'docs' in dependency_groups" + +[[packages]] +name = 'cryptography' +version = '41.0.0' +marker = "'crypto' in extras" + +[[packages]] +name = 'validators' +version = '0.22.0' +marker = "'validation' in extras" + +[[packages]] +name = 'dev-only-tool' +version = '1.0.0' +marker = "'dev' in dependency_groups" +""" + pylock_path = tmp_path / "pylock.toml" + with open(pylock_path, "w") as f: + f.write(pylock_content) + + pylock = PylockFile.from_path(pylock_path) + + # Test 1: No extras, no dependency_groups - only packages without markers + packages = pylock.get_packages_for_environment(extras=set(), dependency_groups=set()) + package_names = [p["name"] for p in packages] + assert "requests" in package_names + assert "pytest" not in package_names + assert "sphinx" not in package_names + assert "cryptography" not in package_names + assert "validators" not in package_names + assert "dev-only-tool" not in package_names + + # Test 2: With 'dev' dependency_group + packages = pylock.get_packages_for_environment(extras=set(), dependency_groups={"dev"}) + package_names = [p["name"] for p in packages] + assert "requests" in package_names + assert "pytest" in package_names # 'dev' in dependency_groups evaluates to True + assert "sphinx" not in package_names # 'docs' not provided + assert "dev-only-tool" in package_names # 'dev' in dependency_groups evaluates to True + assert "cryptography" not in package_names + + # Test 3: With 'docs' dependency_group + packages = pylock.get_packages_for_environment(extras=set(), dependency_groups={"docs"}) + package_names = [p["name"] for p in packages] + assert "requests" in package_names + assert "pytest" not in package_names + assert "sphinx" in package_names # 'docs' in dependency_groups evaluates to True + assert "dev-only-tool" not in package_names + + # Test 4: With 'crypto' extra + packages = pylock.get_packages_for_environment(extras={"crypto"}, dependency_groups=set()) + package_names = [p["name"] for p in packages] + assert "requests" in package_names + assert "cryptography" in package_names # 'crypto' in extras evaluates to True + assert "validators" not in package_names # 'validation' not provided + assert "pytest" not in package_names + + # Test 5: With multiple dependency_groups and extras + packages = pylock.get_packages_for_environment( + extras={"crypto", "validation"}, + dependency_groups={"dev", "docs"} + ) + package_names = [p["name"] for p in packages] + assert "requests" in package_names + assert "pytest" in package_names + assert "sphinx" in package_names + assert "cryptography" in package_names + assert "validators" in package_names + assert "dev-only-tool" in package_names + + +def test_from_lockfile_with_custom_dev_groups(tmp_path): + """Test from_lockfile with custom dev_groups parameter.""" + lockfile_content = { + "_meta": { + "sources": [ + {"name": "pypi", "url": "https://pypi.org/simple/", "verify_ssl": True} + ], + "requires": {"python_version": "3.10"}, + }, + "default": { + "requests": {"version": "==2.28.1", "hashes": ["sha256:abc123"]}, + }, + "develop": { + "pytest": {"version": "==7.0.0", "hashes": ["sha256:def456"]}, + }, + } + + lockfile_path = tmp_path / "Pipfile.lock" + import json + with open(lockfile_path, "w") as f: + json.dump(lockfile_content, f) + + # Test with custom dev groups + pylock = PylockFile.from_lockfile( + lockfile_path, dev_groups=["testing", "development"] + ) + + # Check that the dependency-groups includes our custom groups + assert "testing" in pylock.dependency_groups + assert "development" in pylock.dependency_groups + + # Check that the marker for develop packages uses the custom groups + pytest_pkg = next(p for p in pylock.packages if p["name"] == "pytest") + assert "'testing' in dependency_groups" in pytest_pkg["marker"] + assert "'development' in dependency_groups" in pytest_pkg["marker"] + + +def test_from_lockfile_adds_package_index(tmp_path): + """Test that from_lockfile adds packages.index field (PEP 751).""" + lockfile_content = { + "_meta": { + "sources": [ + {"name": "pypi", "url": "https://pypi.org/simple/", "verify_ssl": True} + ], + "requires": {"python_version": "3.10"}, + }, + "default": { + "requests": {"version": "==2.28.1", "hashes": ["sha256:abc123"]}, + }, + "develop": {}, + } + + lockfile_path = tmp_path / "Pipfile.lock" + import json + with open(lockfile_path, "w") as f: + json.dump(lockfile_content, f) + + pylock = PylockFile.from_lockfile(lockfile_path) + + # Check that packages have index field + requests_pkg = next(p for p in pylock.packages if p["name"] == "requests") + assert "index" in requests_pkg + assert requests_pkg["index"] == "https://pypi.org/simple/" + + +def test_from_pyproject(tmp_path): + """Test creating a PylockFile from pyproject.toml.""" + pyproject_content = ''' +[project] +name = "my-project" +version = "1.0.0" +requires-python = ">=3.9" +dependencies = [ + "requests>=2.28.0", + "click>=8.0.0", +] + +[project.optional-dependencies] +crypto = [ + "cryptography>=40.0.0", +] + +[dependency-groups] +dev = [ + "pytest>=7.0.0", + "black>=23.0.0", +] +''' + pyproject_path = tmp_path / "pyproject.toml" + with open(pyproject_path, "w") as f: + f.write(pyproject_content) + + pylock = PylockFile.from_pyproject(pyproject_path) + + # Check basic metadata + assert pylock.lock_version == "1.0" + assert pylock.created_by == "pipenv" + assert pylock.requires_python == ">=3.9" + + # Check extras + assert "crypto" in pylock.extras + + # Check dependency groups + assert "dev" in pylock.dependency_groups + + # Check packages + package_names = [p["name"] for p in pylock.packages] + assert "requests" in package_names + assert "click" in package_names + assert "cryptography" in package_names + assert "pytest" in package_names + assert "black" in package_names + + # Check markers for extras + crypto_pkg = next(p for p in pylock.packages if p["name"] == "cryptography") + assert "'crypto' in extras" in crypto_pkg["marker"] + + # Check markers for dependency groups + pytest_pkg = next(p for p in pylock.packages if p["name"] == "pytest") + assert "'dev' in dependency_groups" in pytest_pkg["marker"] + + +def test_from_pyproject_missing_file(tmp_path): + """Test from_pyproject raises error for missing file.""" + pyproject_path = tmp_path / "pyproject.toml" + + with pytest.raises(FileNotFoundError): + PylockFile.from_pyproject(pyproject_path)