├── .editorconfig ├── .github └── workflows │ └── ci.yaml ├── .gitignore ├── .python-version ├── Makefile ├── README.md ├── pyproject.toml ├── src ├── __init__.py └── adrpy │ ├── __init__.py │ ├── entrypoints │ ├── __init__.py │ └── cli.py │ ├── injection │ ├── __init__.py │ ├── modules.py │ └── settings.py │ ├── repositories │ ├── __init__.py │ └── adr │ │ ├── __init__.py │ │ ├── base.py │ │ └── repository.py │ ├── services │ ├── __init__.py │ └── template │ │ ├── __init__.py │ │ ├── base.py │ │ └── service.py │ ├── shared_kernel │ ├── __init__.py │ ├── constants.py │ ├── dtos.py │ ├── settings.py │ └── value_objects │ │ ├── __init__.py │ │ └── template.py │ ├── templates │ ├── __init__.py │ ├── initial-adr.md │ └── new-adr.md │ └── use_cases │ ├── __init__.py │ ├── create.py │ └── initialize.py ├── tests ├── __init__.py ├── conftest.py ├── fixtures │ ├── __init__.py │ └── repository.py ├── integration │ ├── __init__.py │ └── repository │ │ └── implementation │ │ ├── __init__.py │ │ └── test_adr_file_repository.py └── unit │ ├── __init__.py │ └── test_settings.py └── uv.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.py] 4 | indent_style = space 5 | indent_size = 4 6 | max_line_length = 100 -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: ADR-Py CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - "**" 7 | tags: 8 | - "v*" 9 | pull_request: 10 | branches: 11 | - "**" 12 | 13 | permissions: 14 | contents: write 15 | 16 | jobs: 17 | test: 18 | runs-on: ${{ matrix.os }} 19 | strategy: 20 | fail-fast: false 21 | matrix: 22 | os: ["ubuntu-latest", "windows-latest"] 23 | python-version: ["3.11", "3.12", "3.13"] 24 | steps: 25 | - uses: actions/checkout@v4 26 | 27 | - name: Install the latest version of uv and set the python version 28 | uses: astral-sh/setup-uv@v6 29 | with: 30 | python-version: ${{ matrix.python-version }} 31 | enable-cache: true 32 | activate-environment: true 33 | 34 | - name: Install project dependencies 35 | run: make sync-deps 36 | 37 | - name: Run lint 38 | run: make lint-ci 39 | 40 | - name: Run tests 41 | run: make test 42 | 43 | publish: 44 | needs: test 45 | if: startsWith(github.ref, 'refs/tags/') 46 | runs-on: ubuntu-latest 47 | steps: 48 | - uses: actions/checkout@v4 49 | 50 | - name: Install the latest version of uv and set the python version 51 | uses: astral-sh/setup-uv@v6 52 | with: 53 | python-version: "3.11" 54 | enable-cache: true 55 | activate-environment: true 56 | 57 | - name: Install project dependencies 58 | run: make sync-deps 59 | 60 | - name: Build package 61 | run: uv build 62 | 63 | - name: Publish to PyPI 64 | run: uv publish --token ${{ secrets.PYPI_TOKEN }} 65 | 66 | - name: Create GitHub Release 67 | uses: softprops/action-gh-release@v1 68 | with: 69 | files: dist/* -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/python 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=python 3 | 4 | ### Python ### 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | *.py[cod] 8 | *$py.class 9 | 10 | # C extensions 11 | *.so 12 | 13 | # Distribution / packaging 14 | .Python 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | wheels/ 27 | share/python-wheels/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | MANIFEST 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .nox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | *.py,cover 54 | .hypothesis/ 55 | .pytest_cache/ 56 | cover/ 57 | 58 | # Translations 59 | *.mo 60 | *.pot 61 | 62 | # Django stuff: 63 | *.log 64 | local_settings.py 65 | db.sqlite3 66 | db.sqlite3-journal 67 | 68 | # Flask stuff: 69 | instance/ 70 | .webassets-cache 71 | 72 | # Scrapy stuff: 73 | .scrapy 74 | 75 | # Sphinx documentation 76 | docs/_build/ 77 | 78 | # PyBuilder 79 | .pybuilder/ 80 | target/ 81 | 82 | # Jupyter Notebook 83 | .ipynb_checkpoints 84 | 85 | # IPython 86 | profile_default/ 87 | ipython_config.py 88 | 89 | # pyenv 90 | # For a library or package, you might want to ignore these files since the code is 91 | # intended to run in multiple environments; otherwise, check them in: 92 | # .python-version 93 | 94 | # pipenv 95 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 96 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 97 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 98 | # install all needed dependencies. 99 | #Pipfile.lock 100 | 101 | # poetry 102 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 103 | # This is especially recommended for binary packages to ensure reproducibility, and is more 104 | # commonly ignored for libraries. 105 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 106 | #poetry.lock 107 | 108 | # pdm 109 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 110 | #pdm.lock 111 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 112 | # in version control. 113 | # https://pdm.fming.dev/#use-with-ide 114 | .pdm.toml 115 | 116 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 117 | __pypackages__/ 118 | 119 | # Celery stuff 120 | celerybeat-schedule 121 | celerybeat.pid 122 | 123 | # SageMath parsed files 124 | *.sage.py 125 | 126 | # Environments 127 | .env 128 | .venv 129 | env/ 130 | venv/ 131 | ENV/ 132 | env.bak/ 133 | venv.bak/ 134 | 135 | # Spyder project settings 136 | .spyderproject 137 | .spyproject 138 | 139 | # Rope project settings 140 | .ropeproject 141 | 142 | # mkdocs documentation 143 | /site 144 | 145 | # mypy 146 | .mypy_cache/ 147 | .dmypy.json 148 | dmypy.json 149 | 150 | # Pyre type checker 151 | .pyre/ 152 | 153 | # pytype static type analyzer 154 | .pytype/ 155 | 156 | # Cython debug symbols 157 | cython_debug/ 158 | 159 | # PyCharm 160 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 161 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 162 | # and can be added to the global gitignore or merged into this file. For a more nuclear 163 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 164 | .idea/ 165 | 166 | ### Python Patch ### 167 | # Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration 168 | poetry.toml 169 | 170 | # ruff 171 | .ruff_cache/ 172 | 173 | # LSP config files 174 | pyrightconfig.json 175 | 176 | # End of https://www.toptal.com/developers/gitignore/api/python 177 | 178 | docs/adr -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | 3.11.4 -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PROJECT_PATH = $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) 2 | SRC_PATH = src 3 | TESTS_PATH = tests 4 | LINT_PATHS = \ 5 | $(SRC_PATH) \ 6 | $(TESTS_PATH) 7 | 8 | sync-deps: 9 | uv sync --frozen --active 10 | 11 | update-deps: 12 | uv lock 13 | 14 | lint: 15 | uv run --active black $(LINT_PATHS) 16 | uv run --active ruff check $(LINT_PATHS) --fix 17 | uv run --active mypy $(LINT_PATHS) 18 | 19 | lint-ci: 20 | uv run --active black --check $(LINT_PATHS) 21 | uv run --active ruff check $(LINT_PATHS) 22 | uv run --active mypy $(LINT_PATHS) 23 | 24 | test: 25 | uv run --active pytest -s 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ADR-py 2 | 3 | > Shout to excellent [adr-tools](https://github.com/npryce/adr-tools) project on which `ADR-py` is based on 4 | 5 | This Python script is designed to help software development teams document their architecture decisions using Architecture Decision Records (ADRs). 6 | ADRs are a lightweight and effective way to capture important decisions made during the design and development of a software system, and to keep track of their rationale and implications over time. 7 | 8 | The script creates ADR files in a predefined format, following the principles of **[Michael Nygard's ADR template](https://github.com/joelparkerhenderson/architecture-decision-record/tree/main/locales/en/templates/decision-record-template-by-michael-nygard)**. 9 | Each ADR file is a Markdown document with a unique name that includes a sequential number and a title, which is automatically generated based on the information provided by the user. 10 | 11 | ## Prerequisites 12 | 13 | - Python 3.11 installed on your system. 14 | - Basic knowledge of command-line interface (CLI) usage. 15 | 16 | ## Installation 17 | 18 | - `pip install adr` 19 | 20 | ## How to Use 21 | 22 | **Usage**: 23 | 24 | ```console 25 | $ adr [OPTIONS] COMMAND [ARGS]... 26 | ``` 27 | 28 | **Options**: 29 | 30 | * `--install-completion`: Install completion for the current shell. 31 | * `--show-completion`: Show completion for the current shell, to copy it or customize the installation. 32 | * `--help`: Show this message and exit. 33 | 34 | **Commands**: 35 | 36 | * `init`: Initialize ADR directory with first ADR in given PATH 37 | * `new`: Create new ADR with given NAME 38 | 39 | ## `init` 40 | 41 | Initialize ADR directory with first ADR in given PATH 42 | 43 | **Usage**: 44 | 45 | ```console 46 | $ adr init [OPTIONS] [PATH] 47 | ``` 48 | 49 | **Arguments**: 50 | 51 | * `[PATH]`: Path in where ADRs should reside. If not provided Path will be extracted from pyproject.toml 52 | 53 | **Options**: 54 | 55 | * `--help`: Show this message and exit. 56 | 57 | ## `new` 58 | 59 | Create new ADR with given NAME 60 | 61 | **Usage**: 62 | 63 | ```console 64 | $ adr new [OPTIONS] NAME 65 | ``` 66 | 67 | **Arguments**: 68 | 69 | * `NAME`: Name of new ADR. Longer names (with spaces) should be put in quotation marks. [required] 70 | 71 | **Options**: 72 | 73 | * `adr --help`: Show this message and exit. 74 | 75 | 76 | ## **ADR Template** 77 | 78 | The generated ADR files follow the template proposed by Michael Nygard in his book "Documenting Architecture Decisions." The template consists of the following sections: 79 | 80 | - Title: The title of the ADR. 81 | - Status: The current status of the decision (e.g., proposed, accepted, rejected). 82 | - Context: The context and background information that led to the decision. 83 | - Decision: The decision made and its rationale. 84 | - Consequences: The potential consequences and trade-offs of the decision. 85 | 86 | ## **Benefits of ADRs** 87 | 88 | Using ADRs has several benefits for software development teams, including: 89 | 90 | - Documentation: ADRs provide a written record of important architectural decisions, making it easier for team members to understand the reasons behind past decisions. 91 | - Communication: ADRs serve as a communication tool for discussing and documenting design decisions, facilitating collaboration among team members. 92 | - Decision-making: ADRs encourage thoughtful decision-making by requiring the team to consider the context, rationale, and potential consequences of each decision. 93 | - Transparency: ADRs promote transparency by making architectural decisions visible and accessible to the entire team, fostering a culture of shared understanding and accountability. 94 | - Knowledge sharing: ADRs help capture the collective knowledge and experience of the team, enabling future team members to learn from past decisions and avoid repeating mistakes. 95 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "adr" 3 | version = "0.4.0" 4 | description = "This Python script is designed to help software development teams document their architecture decisions using Architecture Decision Records (ADRs)." 5 | authors = [{ name = "Daniel Różycki", email = "altosterino@gmail.com" }] 6 | requires-python = ">=3.11,<3.14" 7 | readme = "README.md" 8 | dependencies = [ 9 | "typer==0.16", 10 | "loguru==0.7.3", 11 | "mako==1.3.10", 12 | "lidipy==0.3.1", 13 | ] 14 | 15 | [project.scripts] 16 | adr = "adrpy.entrypoints.cli:cli_entrypoint" 17 | 18 | [dependency-groups] 19 | dev = [ 20 | "black>=25.1.0,<26", 21 | "mypy>=1.15.0,<2", 22 | "pytest>=8.3.5,<9", 23 | "ruff>=0.10.0,<0.11", 24 | ] 25 | 26 | [tool.hatch.build.targets.sdist] 27 | include = ["src/adrpy"] 28 | 29 | [tool.hatch.build.targets.wheel] 30 | include = ["src/adrpy"] 31 | 32 | [tool.hatch.build.targets.wheel.sources] 33 | "src/adrpy" = "adrpy" 34 | 35 | [build-system] 36 | requires = ["hatchling"] 37 | build-backend = "hatchling.build" 38 | 39 | [tool.black] 40 | line_length = 100 41 | 42 | [tool.mypy] 43 | python_version = "3.11" 44 | warn_return_any = true 45 | warn_no_return = true 46 | warn_unused_configs = true 47 | warn_redundant_casts = true 48 | warn_unreachable = true 49 | disallow_untyped_calls = true 50 | disallow_untyped_defs = true 51 | disallow_incomplete_defs = true 52 | check_untyped_defs = true 53 | ignore_missing_imports = true 54 | 55 | [tool.ruff] 56 | line-length = 100 57 | target-version = "py311" 58 | 59 | [tool.ruff.lint] 60 | select = ["E", "F", "I", "PL", "T20"] 61 | 62 | [tool.pytest.ini_options] 63 | addopts = ["--verbose"] 64 | pythonpath = ["src", "tests"] 65 | testpaths = ["tests"] 66 | filterwarnings = ["error"] 67 | 68 | [tool.uv] 69 | required-version = "0.7.8" 70 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlTosterino/ADR-py/e192ce041581a5583180f2e0d32b6172f65824a4/src/__init__.py -------------------------------------------------------------------------------- /src/adrpy/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/adrpy/entrypoints/__init__.py: -------------------------------------------------------------------------------- 1 | from adrpy.injection import setup_injection 2 | 3 | setup_injection() 4 | -------------------------------------------------------------------------------- /src/adrpy/entrypoints/cli.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from typing import Annotated 3 | 4 | import typer 5 | 6 | from adrpy.injection import lidi 7 | from adrpy.shared_kernel.dtos import CreateAdrDto, InitializeAdrDto 8 | from adrpy.shared_kernel.settings import Settings 9 | from adrpy.use_cases.create import CreateAdr 10 | from adrpy.use_cases.initialize import InitializeAdr 11 | 12 | app = typer.Typer() 13 | 14 | 15 | @app.command() 16 | def init( 17 | path: Path = typer.Argument( 18 | None, 19 | help=( 20 | "Path in where ADRs should reside. " 21 | "If not provided, Path will be extracted from pyproject.toml. " 22 | "If no pyproject.toml is found, ADRs will be initialized in the current " 23 | "working directory." 24 | ), 25 | ), 26 | ) -> None: 27 | """ 28 | Initialize ADR directory with first ADR in given PATH 29 | """ 30 | if path: 31 | new_settings = Settings(initial_adr_dir=path) 32 | lidi.bind(Settings, new_settings, singleton=True) 33 | dto = InitializeAdrDto(path=path) 34 | InitializeAdr.execute(dto=dto) 35 | 36 | 37 | @app.command() 38 | def new( 39 | name: Annotated[ 40 | str, 41 | typer.Argument( 42 | help="Name of new ADR. Longer names (with spaces) should be put in quotation marks." 43 | ), 44 | ], 45 | ) -> None: 46 | """ 47 | Create new ADR with given NAME 48 | """ 49 | dto = CreateAdrDto(name=name) 50 | CreateAdr.execute(dto=dto) 51 | 52 | 53 | def cli_entrypoint() -> None: 54 | app() 55 | -------------------------------------------------------------------------------- /src/adrpy/injection/__init__.py: -------------------------------------------------------------------------------- 1 | from lidipy import Lidi 2 | 3 | from adrpy.injection.modules import bind_modules 4 | from adrpy.injection.settings import bind_settings 5 | 6 | lidi = Lidi() 7 | 8 | 9 | def setup_injection() -> None: 10 | bind_settings(lidi) 11 | bind_modules(lidi) 12 | -------------------------------------------------------------------------------- /src/adrpy/injection/modules.py: -------------------------------------------------------------------------------- 1 | from lidipy import Lidi 2 | 3 | 4 | def bind_modules(lidi: Lidi) -> None: 5 | from adrpy.repositories.adr.base import IADRRepository 6 | from adrpy.repositories.adr.repository import ADRFileRepository 7 | from adrpy.services.template.base import ITemplateService 8 | from adrpy.services.template.service import MakoTemplateService 9 | 10 | lidi.bind(IADRRepository, ADRFileRepository()) 11 | lidi.bind(ITemplateService, MakoTemplateService()) 12 | -------------------------------------------------------------------------------- /src/adrpy/injection/settings.py: -------------------------------------------------------------------------------- 1 | from lidipy import Lidi 2 | 3 | 4 | def bind_settings(lidi: Lidi) -> None: 5 | from adrpy.shared_kernel.settings import Settings 6 | 7 | lidi.bind(Settings, Settings(), singleton=True) 8 | -------------------------------------------------------------------------------- /src/adrpy/repositories/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlTosterino/ADR-py/e192ce041581a5583180f2e0d32b6172f65824a4/src/adrpy/repositories/__init__.py -------------------------------------------------------------------------------- /src/adrpy/repositories/adr/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlTosterino/ADR-py/e192ce041581a5583180f2e0d32b6172f65824a4/src/adrpy/repositories/adr/__init__.py -------------------------------------------------------------------------------- /src/adrpy/repositories/adr/base.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | from adrpy.shared_kernel.value_objects.template import RenderedTemplate, Template 4 | 5 | # TODO: Add example how to use adrpy with custom DatabaseRepository 6 | 7 | 8 | class IADRRepository(ABC): 9 | @abstractmethod 10 | def get_template(self, name: str) -> Template: # TODO: Rename to get_app_template 11 | ... 12 | 13 | @abstractmethod 14 | def create(self, adr_name: str, template: RenderedTemplate) -> None: ... 15 | 16 | @abstractmethod 17 | def get_next_ordinal_number(self) -> int: ... 18 | 19 | 20 | # ? Possibility of custom templates? 21 | -------------------------------------------------------------------------------- /src/adrpy/repositories/adr/repository.py: -------------------------------------------------------------------------------- 1 | from typing import Final 2 | 3 | from adrpy.injection import lidi 4 | from adrpy.repositories.adr.base import IADRRepository 5 | from adrpy.shared_kernel.settings import Settings 6 | from adrpy.shared_kernel.value_objects.template import RenderedTemplate, Template 7 | 8 | 9 | class ADRFileRepository(IADRRepository): 10 | SETTINGS: Final[Settings] = lidi.resolve_attr(Settings) 11 | 12 | def get_template(self, name: str) -> Template: 13 | # TODO: Move `get_template` to `ITemplateService` or create persistence repository/facade 14 | template_path = self.SETTINGS.APP_TEMPLATES_DIR / name 15 | with open(template_path, "r") as file: 16 | content = file.read() 17 | return Template(name=name, content=content) 18 | 19 | def create(self, adr_name: str, template: RenderedTemplate) -> None: 20 | self.SETTINGS.adr_dir.mkdir(parents=True, exist_ok=True) 21 | new_adr_path = self.SETTINGS.adr_dir / self.__get_filename_with_extension(name=adr_name) 22 | with open(new_adr_path, "w") as file: 23 | file.write(template.content) 24 | 25 | def get_next_ordinal_number(self) -> int: 26 | ordinal_number = 0 27 | for path in self.SETTINGS.adr_dir.glob("*.md"): 28 | # TODO: Use front matter (metadata) to store ordinal number 29 | filename = path.stem 30 | parts = filename.split("-", 1) 31 | if not parts or not parts[0].isdigit(): 32 | continue 33 | prefix_as_int = int(parts[0]) 34 | ordinal_number = max(ordinal_number, prefix_as_int) 35 | return ordinal_number + 1 36 | 37 | @staticmethod 38 | def __get_filename_with_extension(name: str) -> str: 39 | return f"{name}.md" 40 | -------------------------------------------------------------------------------- /src/adrpy/services/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlTosterino/ADR-py/e192ce041581a5583180f2e0d32b6172f65824a4/src/adrpy/services/__init__.py -------------------------------------------------------------------------------- /src/adrpy/services/template/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlTosterino/ADR-py/e192ce041581a5583180f2e0d32b6172f65824a4/src/adrpy/services/template/__init__.py -------------------------------------------------------------------------------- /src/adrpy/services/template/base.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | from adrpy.shared_kernel.value_objects.template import RenderedTemplate, Template 4 | 5 | 6 | class ITemplateService(ABC): 7 | @abstractmethod 8 | def render(self, template_file: Template, data: dict) -> RenderedTemplate: ... 9 | -------------------------------------------------------------------------------- /src/adrpy/services/template/service.py: -------------------------------------------------------------------------------- 1 | from mako.template import Template as MakoTemplate 2 | 3 | from adrpy.services.template.base import ITemplateService 4 | from adrpy.shared_kernel.value_objects.template import RenderedTemplate, Template 5 | 6 | 7 | class MakoTemplateService(ITemplateService): 8 | def render(self, template_file: Template, data: dict) -> RenderedTemplate: 9 | mako_template = MakoTemplate(template_file.content) 10 | mako_render = mako_template.render(**data) 11 | return RenderedTemplate(name=template_file.name, content=mako_render) 12 | -------------------------------------------------------------------------------- /src/adrpy/shared_kernel/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlTosterino/ADR-py/e192ce041581a5583180f2e0d32b6172f65824a4/src/adrpy/shared_kernel/__init__.py -------------------------------------------------------------------------------- /src/adrpy/shared_kernel/constants.py: -------------------------------------------------------------------------------- 1 | class AppTemplates: 2 | INITIAL_ADR = "initial-adr.md" 3 | SUPERSEDE_ADR = "supersede-adr.md" 4 | NEW_ADR = "new-adr.md" 5 | -------------------------------------------------------------------------------- /src/adrpy/shared_kernel/dtos.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | from pathlib import Path 3 | 4 | from adrpy.shared_kernel.constants import AppTemplates 5 | 6 | 7 | @dataclass(frozen=True) 8 | class InitializeAdrDto: 9 | path: Path | None 10 | adr_template_name: str = field(default=AppTemplates.INITIAL_ADR, init=False) 11 | 12 | 13 | @dataclass(frozen=True) 14 | class CreateAdrDto: 15 | name: str 16 | adr_template_name: str = field(default=AppTemplates.NEW_ADR, init=False) 17 | 18 | @property 19 | def adr_name(self) -> str: 20 | lower_name = self.name.lower() 21 | lower_name_no_spaces = "-".join(lower_name.split()) 22 | return lower_name_no_spaces 23 | 24 | def adr_name_with_ordinal(self, ordinal_number: int) -> str: 25 | return f"{ordinal_number:04d}-{self.adr_name}" 26 | -------------------------------------------------------------------------------- /src/adrpy/shared_kernel/settings.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | from functools import cached_property 3 | from pathlib import Path 4 | 5 | 6 | @dataclass(frozen=True) 7 | class Settings: 8 | # TODO: Add DEBUG logger handler 9 | initial_adr_dir: Path | None = None # TODO: Rename to requested_adr_dir or something 10 | APP_TEMPLATES_DIR: Path = field(init=False, default=Path(__file__).parents[1] / "templates") 11 | 12 | @cached_property 13 | def adr_dir(self) -> Path | None: 14 | if self.initial_adr_dir: 15 | return self.initial_adr_dir 16 | if adr_dir_from_config := self.__get_adr_dir_from_config(): 17 | return adr_dir_from_config 18 | return self.working_directory 19 | 20 | @cached_property 21 | def working_directory(self) -> Path: 22 | return Path.cwd() 23 | 24 | def __get_adr_dir_from_config(self) -> Path | None: 25 | import tomllib 26 | 27 | try: 28 | with open(self.working_directory / "pyproject.toml", "rb") as f: 29 | data = tomllib.load(f) 30 | except FileNotFoundError: 31 | # TODO): Add debug log here 32 | return None 33 | tools = data.get("tool", {}) 34 | adrpy_tool = tools.get("adrpy", {}) 35 | adrpy_dir = adrpy_tool.get("dir", None) 36 | if adrpy_dir: 37 | full_path: Path = self.working_directory / adrpy_dir 38 | return full_path 39 | return None 40 | -------------------------------------------------------------------------------- /src/adrpy/shared_kernel/value_objects/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlTosterino/ADR-py/e192ce041581a5583180f2e0d32b6172f65824a4/src/adrpy/shared_kernel/value_objects/__init__.py -------------------------------------------------------------------------------- /src/adrpy/shared_kernel/value_objects/template.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | @dataclass(frozen=True) 5 | class Template: 6 | name: str 7 | content: str 8 | 9 | 10 | @dataclass(frozen=True) 11 | class RenderedTemplate(Template): 12 | pass 13 | -------------------------------------------------------------------------------- /src/adrpy/templates/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlTosterino/ADR-py/e192ce041581a5583180f2e0d32b6172f65824a4/src/adrpy/templates/__init__.py -------------------------------------------------------------------------------- /src/adrpy/templates/initial-adr.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: ... 3 | tags: ... 4 | 5 | --- 6 | 7 | # 1. Record architecture decisions 8 | 9 | Date: ${ date_created.strftime("%d/%m/%Y %H:%M") } 10 | 11 | <%text>## Status 12 | 13 | ${ status } 14 | 15 | <%text>## Context 16 | 17 | We need to record the architectural decisions made on this project. 18 | 19 | <%text>## Decision 20 | 21 | We will use Architecture Decision Records, as described by Michael Nygard in this article: http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions 22 | 23 | <%text>## Consequences 24 | 25 | See Michael Nygard's article, linked above. -------------------------------------------------------------------------------- /src/adrpy/templates/new-adr.md: -------------------------------------------------------------------------------- 1 | # ${ ordinal_num }. ${ name } 2 | 3 | Date: ${ date_created.strftime("%d/%m/%Y %H:%M") } 4 | 5 | <%text>## Status 6 | 7 | ${ status } 8 | 9 | <%text>## Context 10 | 11 | <%text>## Decision 12 | 13 | <%text>## Consequences -------------------------------------------------------------------------------- /src/adrpy/use_cases/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlTosterino/ADR-py/e192ce041581a5583180f2e0d32b6172f65824a4/src/adrpy/use_cases/__init__.py -------------------------------------------------------------------------------- /src/adrpy/use_cases/create.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import Final 3 | 4 | from adrpy.injection import lidi 5 | from adrpy.repositories.adr.base import IADRRepository 6 | from adrpy.services.template.base import ITemplateService 7 | from adrpy.shared_kernel.dtos import CreateAdrDto 8 | 9 | 10 | class CreateAdr: 11 | TEMPLATE_SERVICE: Final[ITemplateService] = lidi.resolve_attr(ITemplateService) 12 | REPOSITORY: Final[IADRRepository] = lidi.resolve_attr(IADRRepository) 13 | 14 | @classmethod 15 | def execute(cls, dto: CreateAdrDto) -> None: 16 | template = cls.REPOSITORY.get_template(name=dto.adr_template_name) 17 | ordinal_number = cls.REPOSITORY.get_next_ordinal_number() 18 | rendered_template = cls.TEMPLATE_SERVICE.render( 19 | template_file=template, 20 | data={ 21 | "date_created": datetime.now(), 22 | "status": "ACCEPTED", 23 | "name": dto.name, 24 | "ordinal_num": ordinal_number, 25 | }, 26 | ) 27 | adr_name = dto.adr_name_with_ordinal(ordinal_number=ordinal_number) 28 | cls.REPOSITORY.create(adr_name=adr_name, template=rendered_template) 29 | -------------------------------------------------------------------------------- /src/adrpy/use_cases/initialize.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import Final 3 | 4 | from adrpy.injection import lidi 5 | from adrpy.repositories.adr.base import IADRRepository 6 | from adrpy.services.template.base import ITemplateService 7 | from adrpy.shared_kernel.dtos import InitializeAdrDto 8 | 9 | 10 | class InitializeAdr: 11 | TEMPLATE_SERVICE: Final[ITemplateService] = lidi.resolve_attr(ITemplateService) 12 | ADR_REPOSITORY: Final[IADRRepository] = lidi.resolve_attr(IADRRepository) 13 | 14 | INITIAL_ADR_NAME: Final[str] = "0001-record-architecture-decisions" 15 | 16 | @classmethod 17 | def execute(cls, dto: InitializeAdrDto) -> None: 18 | app_template = cls.ADR_REPOSITORY.get_template(name=dto.adr_template_name) 19 | rendered_template = cls.TEMPLATE_SERVICE.render( 20 | template_file=app_template, data={"date_created": datetime.now(), "status": "ACCEPTED"} 21 | ) 22 | cls.ADR_REPOSITORY.create(adr_name=cls.INITIAL_ADR_NAME, template=rendered_template) 23 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlTosterino/ADR-py/e192ce041581a5583180f2e0d32b6172f65824a4/tests/__init__.py -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import shutil 2 | from typing import Generator, Iterator 3 | 4 | import pytest 5 | from lidipy import Lidi 6 | 7 | from adrpy.injection import setup_injection 8 | from tests.fixtures.repository import TEST_DIRECTORY 9 | 10 | pytest_plugins = ["fixtures.repository"] 11 | 12 | 13 | @pytest.fixture 14 | def lidi() -> Generator[Lidi, None, None]: 15 | from adrpy.injection import lidi 16 | 17 | setup_injection() 18 | yield lidi 19 | 20 | 21 | @pytest.fixture(autouse=True) 22 | def remove_test_files() -> Iterator[None]: 23 | yield 24 | shutil.rmtree(TEST_DIRECTORY, ignore_errors=True) 25 | -------------------------------------------------------------------------------- /tests/fixtures/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlTosterino/ADR-py/e192ce041581a5583180f2e0d32b6172f65824a4/tests/fixtures/__init__.py -------------------------------------------------------------------------------- /tests/fixtures/repository.py: -------------------------------------------------------------------------------- 1 | import dataclasses 2 | from pathlib import Path 3 | from typing import Iterator 4 | 5 | import pytest 6 | from lidipy import Lidi 7 | 8 | from adrpy.repositories.adr.base import IADRRepository 9 | from adrpy.repositories.adr.repository import ADRFileRepository 10 | from adrpy.shared_kernel.settings import Settings 11 | 12 | TEST_DIRECTORY = Path(__file__).parent / "testdir" 13 | TEST_FILENAME = "0001-testfile" 14 | TEST_FILENAME_WITH_EXTENSION = "0001-testfile.md" 15 | 16 | 17 | @pytest.fixture() 18 | def adr_repository(lidi: Lidi) -> Iterator[IADRRepository]: 19 | original_repo = lidi.resolve(IADRRepository) 20 | original_settings = lidi.resolve(Settings) 21 | lidi.bind(Settings, dataclasses.replace(original_settings, initial_adr_dir=TEST_DIRECTORY)) 22 | lidi.bind(IADRRepository, ADRFileRepository) 23 | yield lidi.resolve(IADRRepository) 24 | lidi.bind(IADRRepository, original_repo) 25 | lidi.bind(Settings, original_settings) 26 | -------------------------------------------------------------------------------- /tests/integration/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlTosterino/ADR-py/e192ce041581a5583180f2e0d32b6172f65824a4/tests/integration/__init__.py -------------------------------------------------------------------------------- /tests/integration/repository/implementation/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlTosterino/ADR-py/e192ce041581a5583180f2e0d32b6172f65824a4/tests/integration/repository/implementation/__init__.py -------------------------------------------------------------------------------- /tests/integration/repository/implementation/test_adr_file_repository.py: -------------------------------------------------------------------------------- 1 | import dataclasses 2 | 3 | from lidipy import Lidi 4 | 5 | from adrpy.repositories.adr.repository import IADRRepository 6 | from adrpy.shared_kernel.constants import AppTemplates 7 | from adrpy.shared_kernel.settings import Settings 8 | from adrpy.shared_kernel.value_objects.template import RenderedTemplate 9 | from tests.fixtures.repository import TEST_DIRECTORY, TEST_FILENAME, TEST_FILENAME_WITH_EXTENSION 10 | 11 | 12 | def test_should_create_file(adr_repository: IADRRepository) -> None: 13 | # Given 14 | rendered_template = RenderedTemplate(name=TEST_FILENAME, content="TEST_CONTENT") 15 | 16 | # When 17 | adr_repository.create(adr_name=TEST_FILENAME, template=rendered_template) 18 | 19 | # Then 20 | with open(TEST_DIRECTORY / TEST_FILENAME_WITH_EXTENSION, "r") as created_file: 21 | content = created_file.read() 22 | 23 | assert content == rendered_template.content 24 | 25 | 26 | def test_should_get_template_file(adr_repository: IADRRepository) -> None: 27 | # When 28 | template = adr_repository.get_template(name=AppTemplates.INITIAL_ADR) 29 | 30 | # Then 31 | assert template.name == AppTemplates.INITIAL_ADR 32 | assert template.content 33 | 34 | 35 | def test_should_create_file_in_nested_directories( 36 | lidi: Lidi, adr_repository: IADRRepository 37 | ) -> None: 38 | # Given 39 | nested_dir = TEST_DIRECTORY / "nested1" / "nested2" 40 | new_settings = dataclasses.replace(lidi.resolve(Settings), initial_adr_dir=nested_dir) 41 | lidi.bind(Settings, new_settings) 42 | rendered_template = RenderedTemplate(name=TEST_FILENAME, content="TEST_CONTENT") 43 | 44 | # When 45 | adr_repository.create(adr_name=rendered_template.name, template=rendered_template) 46 | 47 | # Then 48 | with open(nested_dir / TEST_FILENAME_WITH_EXTENSION, "r") as created_file: 49 | content = created_file.read() 50 | 51 | assert content == rendered_template.content 52 | 53 | 54 | def test_should_get_next_ordinal_number(adr_repository: IADRRepository) -> None: 55 | # Given 56 | rendered_template = RenderedTemplate(name=TEST_FILENAME, content="TEST_CONTENT") 57 | adr_repository.create(adr_name=TEST_FILENAME, template=rendered_template) 58 | expected_next_ordinal_number = 2 59 | 60 | # When 61 | ordinal_number = adr_repository.get_next_ordinal_number() 62 | 63 | # Then 64 | assert ordinal_number == expected_next_ordinal_number 65 | -------------------------------------------------------------------------------- /tests/unit/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlTosterino/ADR-py/e192ce041581a5583180f2e0d32b6172f65824a4/tests/unit/__init__.py -------------------------------------------------------------------------------- /tests/unit/test_settings.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from typing import Generator 3 | from unittest.mock import patch 4 | 5 | import pytest 6 | 7 | from adrpy.shared_kernel.settings import Settings 8 | 9 | DIR_PATH = Path(__file__).parent 10 | PYPROJECT_PATH = DIR_PATH / Path("pyproject.toml") 11 | PYPROJECT_DATA = """ 12 | [tool.adrpy] 13 | dir = "docs/adr" 14 | """ 15 | PYPROJECT_WRONG_DATA = """ 16 | [tool.adrpython] 17 | dir = "docs/adr" 18 | """ 19 | 20 | 21 | @pytest.fixture(scope="module") 22 | def correct_pyproject_toml() -> Generator[None, None, None]: 23 | with open(PYPROJECT_PATH, "w") as pyproject: 24 | pyproject.write(PYPROJECT_DATA) 25 | yield 26 | Path(PYPROJECT_PATH).unlink(missing_ok=True) 27 | 28 | 29 | @pytest.fixture(scope="module") 30 | def wrong_pyproject_toml() -> Generator[None, None, None]: 31 | with open(PYPROJECT_PATH, "w") as pyproject: 32 | pyproject.write(PYPROJECT_WRONG_DATA) 33 | yield 34 | Path(PYPROJECT_PATH).unlink(missing_ok=True) 35 | 36 | 37 | def test_should_get_adr_dir_from_settings_when_no_initial_dir_set() -> None: 38 | # Given 39 | settings = Settings() 40 | 41 | # When & Then 42 | assert Path.cwd() == settings.adr_dir 43 | 44 | 45 | def test_should_get_adr_dir_from_settings_when_initial_dir_set() -> None: 46 | # Given 47 | settings = Settings(initial_adr_dir=Path(__file__).parent) 48 | 49 | # When & Then 50 | assert settings.adr_dir == Path(__file__).parent 51 | 52 | 53 | def test_should_get_adr_dir_from_pyproject_toml(correct_pyproject_toml: None) -> None: 54 | # Given 55 | with patch.object(Path, "cwd") as path_cwd_mock: 56 | path_cwd_mock.return_value = DIR_PATH 57 | settings = Settings() 58 | adr_dir = settings.adr_dir 59 | 60 | # When & Then 61 | assert adr_dir == DIR_PATH / Path("docs/adr") 62 | 63 | 64 | def test_should_fallback_wrong_adr_dir_from_pyproject_toml_to_working_directory( 65 | wrong_pyproject_toml: None, 66 | ) -> None: 67 | # TODO: Maybe this shouldn't fallback, but raise instead? 68 | # Given 69 | with patch.object(Path, "cwd") as path_cwd_mock: 70 | path_cwd_mock.return_value = DIR_PATH 71 | settings = Settings() 72 | adr_dir = settings.adr_dir 73 | 74 | # When & Then 75 | assert adr_dir == DIR_PATH 76 | -------------------------------------------------------------------------------- /uv.lock: -------------------------------------------------------------------------------- 1 | version = 1 2 | revision = 2 3 | requires-python = ">=3.11, <3.14" 4 | 5 | [[package]] 6 | name = "adr" 7 | version = "0.4.0" 8 | source = { editable = "." } 9 | dependencies = [ 10 | { name = "lidipy" }, 11 | { name = "loguru" }, 12 | { name = "mako" }, 13 | { name = "typer" }, 14 | ] 15 | 16 | [package.dev-dependencies] 17 | dev = [ 18 | { name = "black" }, 19 | { name = "mypy" }, 20 | { name = "pytest" }, 21 | { name = "ruff" }, 22 | ] 23 | 24 | [package.metadata] 25 | requires-dist = [ 26 | { name = "lidipy", specifier = "==0.3.1" }, 27 | { name = "loguru", specifier = "==0.7.3" }, 28 | { name = "mako", specifier = "==1.3.10" }, 29 | { name = "typer", specifier = "==0.16" }, 30 | ] 31 | 32 | [package.metadata.requires-dev] 33 | dev = [ 34 | { name = "black", specifier = ">=25.1.0,<26" }, 35 | { name = "mypy", specifier = ">=1.15.0,<2" }, 36 | { name = "pytest", specifier = ">=8.3.5,<9" }, 37 | { name = "ruff", specifier = ">=0.10.0,<0.11" }, 38 | ] 39 | 40 | [[package]] 41 | name = "black" 42 | version = "25.1.0" 43 | source = { registry = "https://pypi.org/simple" } 44 | dependencies = [ 45 | { name = "click" }, 46 | { name = "mypy-extensions" }, 47 | { name = "packaging" }, 48 | { name = "pathspec" }, 49 | { name = "platformdirs" }, 50 | ] 51 | sdist = { url = "https://files.pythonhosted.org/packages/94/49/26a7b0f3f35da4b5a65f081943b7bcd22d7002f5f0fb8098ec1ff21cb6ef/black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666", size = 649449, upload-time = "2025-01-29T04:15:40.373Z" } 52 | wheels = [ 53 | { url = "https://files.pythonhosted.org/packages/7e/4f/87f596aca05c3ce5b94b8663dbfe242a12843caaa82dd3f85f1ffdc3f177/black-25.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0", size = 1614372, upload-time = "2025-01-29T05:37:11.71Z" }, 54 | { url = "https://files.pythonhosted.org/packages/e7/d0/2c34c36190b741c59c901e56ab7f6e54dad8df05a6272a9747ecef7c6036/black-25.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299", size = 1442865, upload-time = "2025-01-29T05:37:14.309Z" }, 55 | { url = "https://files.pythonhosted.org/packages/21/d4/7518c72262468430ead45cf22bd86c883a6448b9eb43672765d69a8f1248/black-25.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096", size = 1749699, upload-time = "2025-01-29T04:18:17.688Z" }, 56 | { url = "https://files.pythonhosted.org/packages/58/db/4f5beb989b547f79096e035c4981ceb36ac2b552d0ac5f2620e941501c99/black-25.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2", size = 1428028, upload-time = "2025-01-29T04:18:51.711Z" }, 57 | { url = "https://files.pythonhosted.org/packages/83/71/3fe4741df7adf015ad8dfa082dd36c94ca86bb21f25608eb247b4afb15b2/black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b", size = 1650988, upload-time = "2025-01-29T05:37:16.707Z" }, 58 | { url = "https://files.pythonhosted.org/packages/13/f3/89aac8a83d73937ccd39bbe8fc6ac8860c11cfa0af5b1c96d081facac844/black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc", size = 1453985, upload-time = "2025-01-29T05:37:18.273Z" }, 59 | { url = "https://files.pythonhosted.org/packages/6f/22/b99efca33f1f3a1d2552c714b1e1b5ae92efac6c43e790ad539a163d1754/black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f", size = 1783816, upload-time = "2025-01-29T04:18:33.823Z" }, 60 | { url = "https://files.pythonhosted.org/packages/18/7e/a27c3ad3822b6f2e0e00d63d58ff6299a99a5b3aee69fa77cd4b0076b261/black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba", size = 1440860, upload-time = "2025-01-29T04:19:12.944Z" }, 61 | { url = "https://files.pythonhosted.org/packages/98/87/0edf98916640efa5d0696e1abb0a8357b52e69e82322628f25bf14d263d1/black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f", size = 1650673, upload-time = "2025-01-29T05:37:20.574Z" }, 62 | { url = "https://files.pythonhosted.org/packages/52/e5/f7bf17207cf87fa6e9b676576749c6b6ed0d70f179a3d812c997870291c3/black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3", size = 1453190, upload-time = "2025-01-29T05:37:22.106Z" }, 63 | { url = "https://files.pythonhosted.org/packages/e3/ee/adda3d46d4a9120772fae6de454c8495603c37c4c3b9c60f25b1ab6401fe/black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171", size = 1782926, upload-time = "2025-01-29T04:18:58.564Z" }, 64 | { url = "https://files.pythonhosted.org/packages/cc/64/94eb5f45dcb997d2082f097a3944cfc7fe87e071907f677e80788a2d7b7a/black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18", size = 1442613, upload-time = "2025-01-29T04:19:27.63Z" }, 65 | { url = "https://files.pythonhosted.org/packages/09/71/54e999902aed72baf26bca0d50781b01838251a462612966e9fc4891eadd/black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717", size = 207646, upload-time = "2025-01-29T04:15:38.082Z" }, 66 | ] 67 | 68 | [[package]] 69 | name = "click" 70 | version = "8.2.1" 71 | source = { registry = "https://pypi.org/simple" } 72 | dependencies = [ 73 | { name = "colorama", marker = "sys_platform == 'win32'" }, 74 | ] 75 | sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" } 76 | wheels = [ 77 | { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, 78 | ] 79 | 80 | [[package]] 81 | name = "colorama" 82 | version = "0.4.6" 83 | source = { registry = "https://pypi.org/simple" } 84 | sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } 85 | wheels = [ 86 | { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, 87 | ] 88 | 89 | [[package]] 90 | name = "iniconfig" 91 | version = "2.1.0" 92 | source = { registry = "https://pypi.org/simple" } 93 | sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } 94 | wheels = [ 95 | { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, 96 | ] 97 | 98 | [[package]] 99 | name = "lidipy" 100 | version = "0.3.1" 101 | source = { registry = "https://pypi.org/simple" } 102 | sdist = { url = "https://files.pythonhosted.org/packages/d9/c9/242f9360779a4b85adfd445fe1d98aefe4ba2ef1e449cc0c4d1d9a6968c5/lidipy-0.3.1.tar.gz", hash = "sha256:aa7f95d9c96be6dff1fb3acbf8a03ceecc21151f4c8f33e0a06224ef92372149", size = 4906, upload-time = "2024-11-18T08:36:51.105Z" } 103 | wheels = [ 104 | { url = "https://files.pythonhosted.org/packages/de/68/a176ee71ed34326e6807baabc18ba106a5e4dce0041f0969b19ef23e9cb6/lidipy-0.3.1-py3-none-any.whl", hash = "sha256:808dc9a603fb6ef71fd46986a45d3bf58831940aa449fb031ebade7932a4fd73", size = 6002, upload-time = "2024-11-18T08:36:48.884Z" }, 105 | ] 106 | 107 | [[package]] 108 | name = "loguru" 109 | version = "0.7.3" 110 | source = { registry = "https://pypi.org/simple" } 111 | dependencies = [ 112 | { name = "colorama", marker = "sys_platform == 'win32'" }, 113 | { name = "win32-setctime", marker = "sys_platform == 'win32'" }, 114 | ] 115 | sdist = { url = "https://files.pythonhosted.org/packages/3a/05/a1dae3dffd1116099471c643b8924f5aa6524411dc6c63fdae648c4f1aca/loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6", size = 63559, upload-time = "2024-12-06T11:20:56.608Z" } 116 | wheels = [ 117 | { url = "https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595, upload-time = "2024-12-06T11:20:54.538Z" }, 118 | ] 119 | 120 | [[package]] 121 | name = "mako" 122 | version = "1.3.10" 123 | source = { registry = "https://pypi.org/simple" } 124 | dependencies = [ 125 | { name = "markupsafe" }, 126 | ] 127 | sdist = { url = "https://files.pythonhosted.org/packages/9e/38/bd5b78a920a64d708fe6bc8e0a2c075e1389d53bef8413725c63ba041535/mako-1.3.10.tar.gz", hash = "sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28", size = 392474, upload-time = "2025-04-10T12:44:31.16Z" } 128 | wheels = [ 129 | { url = "https://files.pythonhosted.org/packages/87/fb/99f81ac72ae23375f22b7afdb7642aba97c00a713c217124420147681a2f/mako-1.3.10-py3-none-any.whl", hash = "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59", size = 78509, upload-time = "2025-04-10T12:50:53.297Z" }, 130 | ] 131 | 132 | [[package]] 133 | name = "markdown-it-py" 134 | version = "3.0.0" 135 | source = { registry = "https://pypi.org/simple" } 136 | dependencies = [ 137 | { name = "mdurl" }, 138 | ] 139 | sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } 140 | wheels = [ 141 | { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, 142 | ] 143 | 144 | [[package]] 145 | name = "markupsafe" 146 | version = "3.0.2" 147 | source = { registry = "https://pypi.org/simple" } 148 | sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } 149 | wheels = [ 150 | { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353, upload-time = "2024-10-18T15:21:02.187Z" }, 151 | { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392, upload-time = "2024-10-18T15:21:02.941Z" }, 152 | { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984, upload-time = "2024-10-18T15:21:03.953Z" }, 153 | { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120, upload-time = "2024-10-18T15:21:06.495Z" }, 154 | { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032, upload-time = "2024-10-18T15:21:07.295Z" }, 155 | { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057, upload-time = "2024-10-18T15:21:08.073Z" }, 156 | { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359, upload-time = "2024-10-18T15:21:09.318Z" }, 157 | { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306, upload-time = "2024-10-18T15:21:10.185Z" }, 158 | { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094, upload-time = "2024-10-18T15:21:11.005Z" }, 159 | { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521, upload-time = "2024-10-18T15:21:12.911Z" }, 160 | { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload-time = "2024-10-18T15:21:13.777Z" }, 161 | { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload-time = "2024-10-18T15:21:14.822Z" }, 162 | { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload-time = "2024-10-18T15:21:15.642Z" }, 163 | { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118, upload-time = "2024-10-18T15:21:17.133Z" }, 164 | { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993, upload-time = "2024-10-18T15:21:18.064Z" }, 165 | { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178, upload-time = "2024-10-18T15:21:18.859Z" }, 166 | { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319, upload-time = "2024-10-18T15:21:19.671Z" }, 167 | { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload-time = "2024-10-18T15:21:20.971Z" }, 168 | { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload-time = "2024-10-18T15:21:22.646Z" }, 169 | { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload-time = "2024-10-18T15:21:23.499Z" }, 170 | { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" }, 171 | { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" }, 172 | { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" }, 173 | { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" }, 174 | { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" }, 175 | { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" }, 176 | { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" }, 177 | { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" }, 178 | { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" }, 179 | { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" }, 180 | { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" }, 181 | { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" }, 182 | { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" }, 183 | { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" }, 184 | { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" }, 185 | { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" }, 186 | { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" }, 187 | { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" }, 188 | { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" }, 189 | { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" }, 190 | ] 191 | 192 | [[package]] 193 | name = "mdurl" 194 | version = "0.1.2" 195 | source = { registry = "https://pypi.org/simple" } 196 | sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } 197 | wheels = [ 198 | { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, 199 | ] 200 | 201 | [[package]] 202 | name = "mypy" 203 | version = "1.16.0" 204 | source = { registry = "https://pypi.org/simple" } 205 | dependencies = [ 206 | { name = "mypy-extensions" }, 207 | { name = "pathspec" }, 208 | { name = "typing-extensions" }, 209 | ] 210 | sdist = { url = "https://files.pythonhosted.org/packages/d4/38/13c2f1abae94d5ea0354e146b95a1be9b2137a0d506728e0da037c4276f6/mypy-1.16.0.tar.gz", hash = "sha256:84b94283f817e2aa6350a14b4a8fb2a35a53c286f97c9d30f53b63620e7af8ab", size = 3323139, upload-time = "2025-05-29T13:46:12.532Z" } 211 | wheels = [ 212 | { url = "https://files.pythonhosted.org/packages/24/c4/ff2f79db7075c274fe85b5fff8797d29c6b61b8854c39e3b7feb556aa377/mypy-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9f826aaa7ff8443bac6a494cf743f591488ea940dd360e7dd330e30dd772a5ab", size = 10884498, upload-time = "2025-05-29T13:18:54.066Z" }, 213 | { url = "https://files.pythonhosted.org/packages/02/07/12198e83006235f10f6a7808917376b5d6240a2fd5dce740fe5d2ebf3247/mypy-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:82d056e6faa508501af333a6af192c700b33e15865bda49611e3d7d8358ebea2", size = 10011755, upload-time = "2025-05-29T13:34:00.851Z" }, 214 | { url = "https://files.pythonhosted.org/packages/f1/9b/5fd5801a72b5d6fb6ec0105ea1d0e01ab2d4971893076e558d4b6d6b5f80/mypy-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:089bedc02307c2548eb51f426e085546db1fa7dd87fbb7c9fa561575cf6eb1ff", size = 11800138, upload-time = "2025-05-29T13:32:55.082Z" }, 215 | { url = "https://files.pythonhosted.org/packages/2e/81/a117441ea5dfc3746431e51d78a4aca569c677aa225bca2cc05a7c239b61/mypy-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6a2322896003ba66bbd1318c10d3afdfe24e78ef12ea10e2acd985e9d684a666", size = 12533156, upload-time = "2025-05-29T13:19:12.963Z" }, 216 | { url = "https://files.pythonhosted.org/packages/3f/38/88ec57c6c86014d3f06251e00f397b5a7daa6888884d0abf187e4f5f587f/mypy-1.16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:021a68568082c5b36e977d54e8f1de978baf401a33884ffcea09bd8e88a98f4c", size = 12742426, upload-time = "2025-05-29T13:20:22.72Z" }, 217 | { url = "https://files.pythonhosted.org/packages/bd/53/7e9d528433d56e6f6f77ccf24af6ce570986c2d98a5839e4c2009ef47283/mypy-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:54066fed302d83bf5128632d05b4ec68412e1f03ef2c300434057d66866cea4b", size = 9478319, upload-time = "2025-05-29T13:21:17.582Z" }, 218 | { url = "https://files.pythonhosted.org/packages/70/cf/158e5055e60ca2be23aec54a3010f89dcffd788732634b344fc9cb1e85a0/mypy-1.16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c5436d11e89a3ad16ce8afe752f0f373ae9620841c50883dc96f8b8805620b13", size = 11062927, upload-time = "2025-05-29T13:35:52.328Z" }, 219 | { url = "https://files.pythonhosted.org/packages/94/34/cfff7a56be1609f5d10ef386342ce3494158e4d506516890142007e6472c/mypy-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f2622af30bf01d8fc36466231bdd203d120d7a599a6d88fb22bdcb9dbff84090", size = 10083082, upload-time = "2025-05-29T13:35:33.378Z" }, 220 | { url = "https://files.pythonhosted.org/packages/b3/7f/7242062ec6288c33d8ad89574df87c3903d394870e5e6ba1699317a65075/mypy-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d045d33c284e10a038f5e29faca055b90eee87da3fc63b8889085744ebabb5a1", size = 11828306, upload-time = "2025-05-29T13:21:02.164Z" }, 221 | { url = "https://files.pythonhosted.org/packages/6f/5f/b392f7b4f659f5b619ce5994c5c43caab3d80df2296ae54fa888b3d17f5a/mypy-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b4968f14f44c62e2ec4a038c8797a87315be8df7740dc3ee8d3bfe1c6bf5dba8", size = 12702764, upload-time = "2025-05-29T13:20:42.826Z" }, 222 | { url = "https://files.pythonhosted.org/packages/9b/c0/7646ef3a00fa39ac9bc0938626d9ff29d19d733011be929cfea59d82d136/mypy-1.16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:eb14a4a871bb8efb1e4a50360d4e3c8d6c601e7a31028a2c79f9bb659b63d730", size = 12896233, upload-time = "2025-05-29T13:18:37.446Z" }, 223 | { url = "https://files.pythonhosted.org/packages/6d/38/52f4b808b3fef7f0ef840ee8ff6ce5b5d77381e65425758d515cdd4f5bb5/mypy-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:bd4e1ebe126152a7bbaa4daedd781c90c8f9643c79b9748caa270ad542f12bec", size = 9565547, upload-time = "2025-05-29T13:20:02.836Z" }, 224 | { url = "https://files.pythonhosted.org/packages/97/9c/ca03bdbefbaa03b264b9318a98950a9c683e06472226b55472f96ebbc53d/mypy-1.16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a9e056237c89f1587a3be1a3a70a06a698d25e2479b9a2f57325ddaaffc3567b", size = 11059753, upload-time = "2025-05-29T13:18:18.167Z" }, 225 | { url = "https://files.pythonhosted.org/packages/36/92/79a969b8302cfe316027c88f7dc6fee70129490a370b3f6eb11d777749d0/mypy-1.16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0b07e107affb9ee6ce1f342c07f51552d126c32cd62955f59a7db94a51ad12c0", size = 10073338, upload-time = "2025-05-29T13:19:48.079Z" }, 226 | { url = "https://files.pythonhosted.org/packages/14/9b/a943f09319167da0552d5cd722104096a9c99270719b1afeea60d11610aa/mypy-1.16.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c6fb60cbd85dc65d4d63d37cb5c86f4e3a301ec605f606ae3a9173e5cf34997b", size = 11827764, upload-time = "2025-05-29T13:46:04.47Z" }, 227 | { url = "https://files.pythonhosted.org/packages/ec/64/ff75e71c65a0cb6ee737287c7913ea155845a556c64144c65b811afdb9c7/mypy-1.16.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7e32297a437cc915599e0578fa6bc68ae6a8dc059c9e009c628e1c47f91495d", size = 12701356, upload-time = "2025-05-29T13:35:13.553Z" }, 228 | { url = "https://files.pythonhosted.org/packages/0a/ad/0e93c18987a1182c350f7a5fab70550852f9fabe30ecb63bfbe51b602074/mypy-1.16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:afe420c9380ccec31e744e8baff0d406c846683681025db3531b32db56962d52", size = 12900745, upload-time = "2025-05-29T13:17:24.409Z" }, 229 | { url = "https://files.pythonhosted.org/packages/28/5d/036c278d7a013e97e33f08c047fe5583ab4f1fc47c9a49f985f1cdd2a2d7/mypy-1.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:55f9076c6ce55dd3f8cd0c6fff26a008ca8e5131b89d5ba6d86bd3f47e736eeb", size = 9572200, upload-time = "2025-05-29T13:33:44.92Z" }, 230 | { url = "https://files.pythonhosted.org/packages/99/a3/6ed10530dec8e0fdc890d81361260c9ef1f5e5c217ad8c9b21ecb2b8366b/mypy-1.16.0-py3-none-any.whl", hash = "sha256:29e1499864a3888bca5c1542f2d7232c6e586295183320caa95758fc84034031", size = 2265773, upload-time = "2025-05-29T13:35:18.762Z" }, 231 | ] 232 | 233 | [[package]] 234 | name = "mypy-extensions" 235 | version = "1.1.0" 236 | source = { registry = "https://pypi.org/simple" } 237 | sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } 238 | wheels = [ 239 | { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, 240 | ] 241 | 242 | [[package]] 243 | name = "packaging" 244 | version = "25.0" 245 | source = { registry = "https://pypi.org/simple" } 246 | sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } 247 | wheels = [ 248 | { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, 249 | ] 250 | 251 | [[package]] 252 | name = "pathspec" 253 | version = "0.12.1" 254 | source = { registry = "https://pypi.org/simple" } 255 | sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } 256 | wheels = [ 257 | { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, 258 | ] 259 | 260 | [[package]] 261 | name = "platformdirs" 262 | version = "4.3.8" 263 | source = { registry = "https://pypi.org/simple" } 264 | sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362, upload-time = "2025-05-07T22:47:42.121Z" } 265 | wheels = [ 266 | { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567, upload-time = "2025-05-07T22:47:40.376Z" }, 267 | ] 268 | 269 | [[package]] 270 | name = "pluggy" 271 | version = "1.6.0" 272 | source = { registry = "https://pypi.org/simple" } 273 | sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } 274 | wheels = [ 275 | { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, 276 | ] 277 | 278 | [[package]] 279 | name = "pygments" 280 | version = "2.19.1" 281 | source = { registry = "https://pypi.org/simple" } 282 | sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581, upload-time = "2025-01-06T17:26:30.443Z" } 283 | wheels = [ 284 | { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" }, 285 | ] 286 | 287 | [[package]] 288 | name = "pytest" 289 | version = "8.3.5" 290 | source = { registry = "https://pypi.org/simple" } 291 | dependencies = [ 292 | { name = "colorama", marker = "sys_platform == 'win32'" }, 293 | { name = "iniconfig" }, 294 | { name = "packaging" }, 295 | { name = "pluggy" }, 296 | ] 297 | sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload-time = "2025-03-02T12:54:54.503Z" } 298 | wheels = [ 299 | { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload-time = "2025-03-02T12:54:52.069Z" }, 300 | ] 301 | 302 | [[package]] 303 | name = "rich" 304 | version = "14.0.0" 305 | source = { registry = "https://pypi.org/simple" } 306 | dependencies = [ 307 | { name = "markdown-it-py" }, 308 | { name = "pygments" }, 309 | ] 310 | sdist = { url = "https://files.pythonhosted.org/packages/a1/53/830aa4c3066a8ab0ae9a9955976fb770fe9c6102117c8ec4ab3ea62d89e8/rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725", size = 224078, upload-time = "2025-03-30T14:15:14.23Z" } 311 | wheels = [ 312 | { url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229, upload-time = "2025-03-30T14:15:12.283Z" }, 313 | ] 314 | 315 | [[package]] 316 | name = "ruff" 317 | version = "0.10.0" 318 | source = { registry = "https://pypi.org/simple" } 319 | sdist = { url = "https://files.pythonhosted.org/packages/4c/ec/9c59d2956566517c98ac8267554f4eaceafb2a19710a429368518b7fab43/ruff-0.10.0.tar.gz", hash = "sha256:fa1554e18deaf8aa097dbcfeafaf38b17a2a1e98fdc18f50e62e8a836abee392", size = 3789921, upload-time = "2025-03-13T18:38:05.228Z" } 320 | wheels = [ 321 | { url = "https://files.pythonhosted.org/packages/bf/3f/742afe91b43def2a75990b293c676355576c0ff9cdbcf4249f78fa592544/ruff-0.10.0-py3-none-linux_armv6l.whl", hash = "sha256:46a2aa0eaae5048e5f804f0be9489d8a661633e23277b7293089e70d5c1a35c4", size = 10078369, upload-time = "2025-03-13T18:37:20.499Z" }, 322 | { url = "https://files.pythonhosted.org/packages/8d/a0/8696fb4862e82f7b40bbbc2917137594b22826cc62d77278a91391507514/ruff-0.10.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:775a6bc61af9dd0a2e1763406522a137e62aabb743d8b43ed95f019cdd1526c7", size = 10876912, upload-time = "2025-03-13T18:37:24.184Z" }, 323 | { url = "https://files.pythonhosted.org/packages/40/aa/0d48b7b7d7a1f168bb8fd893ed559d633c7d68c4a8ef9b996f0c2bd07aca/ruff-0.10.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:8b03e6fcd39d20f0004f9956f0ed5eadc404d3a299f9d9286323884e3b663730", size = 10229962, upload-time = "2025-03-13T18:37:28.211Z" }, 324 | { url = "https://files.pythonhosted.org/packages/21/de/861ced2f75b045d8cfc038d68961d8ac117344df1f43a11abdd05bf7991b/ruff-0.10.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:621101d1af80248827f2409a78c8177c8319986a57b4663613b9c72f8617bfcd", size = 10404627, upload-time = "2025-03-13T18:37:30.626Z" }, 325 | { url = "https://files.pythonhosted.org/packages/21/69/666e0b840191c3ce433962c0d05fc0f6800afe259ea5d230cc731655d8e2/ruff-0.10.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e2dfe85cb6bfbd4259801e7d4982f2a72bdbd5749dc73a09d68a6dbf77f2209a", size = 9939383, upload-time = "2025-03-13T18:37:33.186Z" }, 326 | { url = "https://files.pythonhosted.org/packages/76/bf/34a2adc58092c99cdfa9f1303acd82d840d56412022e477e2ab20c261d2d/ruff-0.10.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43ac3879a20c22fdc57e559f0bb27f0c71828656841d0b42d3505b1e5b3a83c8", size = 11492269, upload-time = "2025-03-13T18:37:35.377Z" }, 327 | { url = "https://files.pythonhosted.org/packages/31/3d/f7ccfcf69f15948623b190feea9d411d5029ae39725fcc078f8d43bd07a6/ruff-0.10.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:ef5e3aac421bbc62f8a7aab21edd49a359ed42205f7a5091a74386bca1efa293", size = 12186939, upload-time = "2025-03-13T18:37:38.381Z" }, 328 | { url = "https://files.pythonhosted.org/packages/6e/3e/c557c0abfdea85c7d238a3cb238c73e7b6d17c30a584234c4fd8fe2cafb6/ruff-0.10.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9f4f62d7fac8b748fce67ad308116b4d4cc1a9f964b4804fc5408fbd06e13ba9", size = 11655896, upload-time = "2025-03-13T18:37:40.753Z" }, 329 | { url = "https://files.pythonhosted.org/packages/3b/8e/3bfa110f37e5192eb3943f14943d05fbb9a76fea380aa87655e6f6276a54/ruff-0.10.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:02f9f6205c5b0d626f98da01a0e75b724a64c21c554bba24b12522c9e9ba6a04", size = 13885502, upload-time = "2025-03-13T18:37:43.226Z" }, 330 | { url = "https://files.pythonhosted.org/packages/51/4a/22cdab59b5563dd7f4c504d0f1e6bb25fc800a5a057395bc24f8ff3a85b2/ruff-0.10.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46a97f3d55f68464c48d1e929a8582c7e5bb80ac73336bbc7b0da894d8e6cd9e", size = 11344767, upload-time = "2025-03-13T18:37:46.656Z" }, 331 | { url = "https://files.pythonhosted.org/packages/3d/0f/8f85de2ac565f82f47c6d8fb7ae04383e6300560f2d1b91c1268ff91e507/ruff-0.10.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a0b811197d0dc96c13d610f8cfdc56030b405bcff5c2f10eab187b329da0ca4a", size = 10300331, upload-time = "2025-03-13T18:37:48.682Z" }, 332 | { url = "https://files.pythonhosted.org/packages/90/4a/b337df327832cb30bd8607e8d1fdf1b6b5ca228307d5008dd49028fb66ae/ruff-0.10.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a13a3fda0870c1c964b47ff5d73805ae80d2a9de93ee2d185d453b8fddf85a84", size = 9926551, upload-time = "2025-03-13T18:37:50.698Z" }, 333 | { url = "https://files.pythonhosted.org/packages/d7/e9/141233730b85675ac806c4b62f70516bd9c0aae8a55823f3a6589ed411be/ruff-0.10.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6ceb8d9f062e90ddcbad929f6136edf764bbf6411420a07e8357602ea28cd99f", size = 10925061, upload-time = "2025-03-13T18:37:53.28Z" }, 334 | { url = "https://files.pythonhosted.org/packages/24/09/02987935b55c2d353a226ac1b4f9718830e2e195834929f46c07eeede746/ruff-0.10.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:c41d07d573617ed2f287ea892af2446fd8a8d877481e8e1ba6928e020665d240", size = 11394949, upload-time = "2025-03-13T18:37:55.375Z" }, 335 | { url = "https://files.pythonhosted.org/packages/d6/ec/054f9879fb6f4122d43ffe5c9f88c8c323a9cd14220d5c813aea5805e02c/ruff-0.10.0-py3-none-win32.whl", hash = "sha256:76e2de0cbdd587e373cd3b4050d2c45babdd7014c1888a6f121c29525c748a15", size = 10272077, upload-time = "2025-03-13T18:37:57.913Z" }, 336 | { url = "https://files.pythonhosted.org/packages/6e/49/915d8682f24645b904fe6a1aac36101464fc814923fdf293c1388dc5533c/ruff-0.10.0-py3-none-win_amd64.whl", hash = "sha256:f943acdecdcc6786a8d1dad455dd9f94e6d57ccc115be4993f9b52ef8316027a", size = 11393300, upload-time = "2025-03-13T18:38:00.414Z" }, 337 | { url = "https://files.pythonhosted.org/packages/82/ed/5c59941634c9026ceeccc7c119f23f4356f09aafd28c15c1bc734ac66b01/ruff-0.10.0-py3-none-win_arm64.whl", hash = "sha256:935a943bdbd9ff0685acd80d484ea91088e27617537b5f7ef8907187d19d28d0", size = 10510133, upload-time = "2025-03-13T18:38:02.91Z" }, 338 | ] 339 | 340 | [[package]] 341 | name = "shellingham" 342 | version = "1.5.4" 343 | source = { registry = "https://pypi.org/simple" } 344 | sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } 345 | wheels = [ 346 | { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, 347 | ] 348 | 349 | [[package]] 350 | name = "typer" 351 | version = "0.16.0" 352 | source = { registry = "https://pypi.org/simple" } 353 | dependencies = [ 354 | { name = "click" }, 355 | { name = "rich" }, 356 | { name = "shellingham" }, 357 | { name = "typing-extensions" }, 358 | ] 359 | sdist = { url = "https://files.pythonhosted.org/packages/c5/8c/7d682431efca5fd290017663ea4588bf6f2c6aad085c7f108c5dbc316e70/typer-0.16.0.tar.gz", hash = "sha256:af377ffaee1dbe37ae9440cb4e8f11686ea5ce4e9bae01b84ae7c63b87f1dd3b", size = 102625, upload-time = "2025-05-26T14:30:31.824Z" } 360 | wheels = [ 361 | { url = "https://files.pythonhosted.org/packages/76/42/3efaf858001d2c2913de7f354563e3a3a2f0decae3efe98427125a8f441e/typer-0.16.0-py3-none-any.whl", hash = "sha256:1f79bed11d4d02d4310e3c1b7ba594183bcedb0ac73b27a9e5f28f6fb5b98855", size = 46317, upload-time = "2025-05-26T14:30:30.523Z" }, 362 | ] 363 | 364 | [[package]] 365 | name = "typing-extensions" 366 | version = "4.13.2" 367 | source = { registry = "https://pypi.org/simple" } 368 | sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" } 369 | wheels = [ 370 | { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" }, 371 | ] 372 | 373 | [[package]] 374 | name = "win32-setctime" 375 | version = "1.2.0" 376 | source = { registry = "https://pypi.org/simple" } 377 | sdist = { url = "https://files.pythonhosted.org/packages/b3/8f/705086c9d734d3b663af0e9bb3d4de6578d08f46b1b101c2442fd9aecaa2/win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0", size = 4867, upload-time = "2024-12-07T15:28:28.314Z" } 378 | wheels = [ 379 | { url = "https://files.pythonhosted.org/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390", size = 4083, upload-time = "2024-12-07T15:28:26.465Z" }, 380 | ] 381 | --------------------------------------------------------------------------------