diff --git a/.github/workflows/ci-pip-tools.yml b/.github/workflows/ci-pip-tools.yml deleted file mode 100644 index 056b486..0000000 --- a/.github/workflows/ci-pip-tools.yml +++ /dev/null @@ -1,67 +0,0 @@ -name: Tests (pip-tools) - -on: - push: - branches: [main] - pull_request: - workflow_dispatch: - -jobs: - test-pip-tools: - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: [windows-latest, ubuntu-latest, macos-latest] - python-version: ['3.14'] - - steps: - - uses: actions/checkout@v6 - - - uses: actions/setup-python@v6 - with: - python-version: ${{ matrix.python-version }} - cache: pip - - - name: Install dependencies - run: pip install -r dev-requirements.txt - - - name: Run unit tests - run: pytest - - - name: Create project from template - # Choose default options - run: cookiecutter . --no-input packaging=pip-tools rse_team_as_coauthor=true use_bsd3_license=true - - - name: Install project dependencies - working-directory: my_project - run: | - git init - pip install --upgrade pip - pip install -r dev-requirements.txt - pip install -r doc-requirements.txt - pip install -e . - - - name: Run mypy - working-directory: my_project - run: mypy . - - - name: Run tests - working-directory: my_project - run: pytest - - - name: Run pre-commit hooks for project - working-directory: my_project - run: | - git add . - pre-commit run -a - - - name: Test docs build successfully - working-directory: my_project - run: mkdocs build --strict - - - name: Test build is successful - working-directory: my_project - run: | - pip install --upgrade build - python -m build diff --git a/.github/workflows/ci-poetry.yml b/.github/workflows/ci-poetry.yml deleted file mode 100644 index 69824ac..0000000 --- a/.github/workflows/ci-poetry.yml +++ /dev/null @@ -1,64 +0,0 @@ -name: Tests (poetry) - -on: - push: - branches: [main] - pull_request: - workflow_dispatch: - -jobs: - test-poetry: - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: [windows-latest, ubuntu-latest, macos-latest] - python-version: ['3.14'] - - steps: - - uses: actions/checkout@v6 - - - name: Install Poetry - run: pipx install poetry - - - uses: actions/setup-python@v6 - with: - python-version: ${{ matrix.python-version }} - cache: pip - - - name: Install dependencies - run: pip install -r dev-requirements.txt - - - name: Run unit tests - run: pytest - - - name: Create project from template - # Choose default options - run: cookiecutter . --no-input packaging=poetry rse_team_as_coauthor=true use_bsd3_license=true - - - name: Install project dependencies - working-directory: my_project - run: poetry install - - - name: Run mypy - working-directory: my_project - run: poetry run mypy . - - - name: Run tests - working-directory: my_project - run: poetry run pytest - - - name: Run pre-commit hooks for project - working-directory: my_project - run: | - git init - git add . - poetry run pre-commit run -a - - - name: Test docs build successfully - working-directory: my_project - run: poetry run mkdocs build --strict - - - name: Test build is successful - working-directory: my_project - run: poetry build diff --git a/.github/workflows/ci-uv.yml b/.github/workflows/ci-uv.yml index 427a5b8..53a06a6 100644 --- a/.github/workflows/ci-uv.yml +++ b/.github/workflows/ci-uv.yml @@ -25,20 +25,15 @@ jobs: prune-cache: false python-version: ${{ matrix.python-version }} - - uses: actions/setup-python@v6 - with: - python-version: ${{ matrix.python-version }} - cache: pip - - name: Install dependencies - run: pip install -r dev-requirements.txt + run: uv sync - name: Run unit tests - run: pytest + run: uv run -m pytest - name: Create project from template # Choose default options - run: cookiecutter . --no-input packaging=uv rse_team_as_coauthor=true use_bsd3_license=true + run: uv run cookiecutter . --no-input packaging=uv rse_team_as_coauthor=true use_bsd3_license=true - name: Install project dependencies working-directory: my_project @@ -52,7 +47,7 @@ jobs: - name: Run tests working-directory: my_project - run: uv run pytest + run: uv run -m pytest - name: Run pre-commit hooks for project working-directory: my_project diff --git a/README.md b/README.md index c3088bf..58bedaf 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@ -[![poetry](https://github.com/ImperialCollegeLondon/python-template/actions/workflows/ci-poetry.yml/badge.svg)](https://github.com/ImperialCollegeLondon/python-template/actions/workflows/ci-poetry.yml) -[![pip-tools](https://github.com/ImperialCollegeLondon/python-template/actions/workflows/ci-pip-tools.yml/badge.svg)](https://github.com/ImperialCollegeLondon/python-template/actions/workflows/ci-pip-tools.yml) + [![uv](https://github.com/ImperialCollegeLondon/python-template/actions/workflows/ci-uv.yml/badge.svg)](https://github.com/ImperialCollegeLondon/python-template/actions/workflows/ci-uv.yml) # Python project template @@ -9,8 +8,7 @@ This repo contains a [`cookiecutter`] template for a Python project, including [`pre-commit`] hooks for linting and GitHub Actions for automatically running tests using [`pytest`]. -The template supports three Python packaging tools: [`poetry`], [`pip-tools`] -and [`uv`]. +The template supports the Python packaging tool [`uv`]. It is particularly geared towards the needs of Imperial College's [Research Software Engineering team], but hopefully it should be generically useful. If you find any @@ -19,8 +17,6 @@ problems or have any questions, please [raise an issue]. [`cookiecutter`]: https://cookiecutter.readthedocs.io/en/stable/ [`pre-commit`]: https://pre-commit.com/ [`pytest`]: https://pytest.org/ -[`poetry`]: https://python-poetry.org/ -[`pip-tools`]: https://github.com/jazzband/pip-tools [`uv`]: https://docs.astral.sh/uv/ [Research Software Engineering team]: https://www.imperial.ac.uk/admin-services/ict/self-service/research-support/rcs/service-offering/research-software-engineering/ [raise an issue]: https://github.com/ImperialCollegeLondon/python-template/issues/new @@ -30,13 +26,13 @@ problems or have any questions, please [raise an issue]. To use this template for your own application: 1. [Install `cookiecutter`] following the instructions for your OS. -2. Create your own project using this template: `cookiecutter - gh:ImperialCollegeLondon/python-template` -3. Choose the options you want for your project -4. To get started, follow the instructions in the readme of the newly created project +2. [Install `uv`] followiung the instructions for your OS. +3. Create your own project using this template: `cookiecutter +gh:ImperialCollegeLondon/python-template` +4. Choose the options you want for your project +5. To get started, follow the instructions in the readme of the newly created project -[Install `cookiecutter`]: - https://cookiecutter.readthedocs.io/en/stable/README.html#installation +[Install `cookiecutter`]: https://cookiecutter.readthedocs.io/en/stable/README.html#installation # Extra setup diff --git a/cookiecutter.json b/cookiecutter.json index fdf5b17..423466f 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -6,11 +6,7 @@ "author_email": "jane_doe@imperial.ac.uk", "rse_team_as_coauthor": false, "use_bsd3_license": false, - "packaging": [ - "poetry", - "pip-tools", - "uv" - ], + "packaging": "uv", "mkdocs": true, "add_precommit_workflows": true, "automerge_bot_prs": false, diff --git a/dev-requirements.txt b/dev-requirements.txt deleted file mode 100644 index 8890227..0000000 --- a/dev-requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -# This file contains dependencies for development and CI workflows -cookiecutter==2.7.1 -pytest==9.0.3 diff --git a/hooks/post_gen_project.py b/hooks/post_gen_project.py index 53d4b00..c63bfb9 100644 --- a/hooks/post_gen_project.py +++ b/hooks/post_gen_project.py @@ -1,5 +1,5 @@ +import subprocess import os -import re from glob import glob from shutil import rmtree @@ -7,79 +7,56 @@ "{% if not cookiecutter.use_bsd3_license %}LICENSE{% endif %}", "{% if not cookiecutter.add_precommit_workflows %}.github/workflows/pre-commit*.yml{% endif %}", "{% if not cookiecutter.automerge_bot_prs %}.github/workflows/auto-merge.yml{% endif %}", - "{% if cookiecutter.packaging != 'pip-tools' %}requirements.txt{% endif %}", - "{% if cookiecutter.packaging != 'pip-tools' %}dev-requirements.txt{% endif %}", - "{% if cookiecutter.packaging != 'pip-tools' or not cookiecutter.mkdocs %}doc-requirements.txt{% endif %}", "{% if not cookiecutter.mkdocs %}docs{% endif %}", "{% if not cookiecutter.mkdocs %}.github/workflows/docs.yml{% endif %}", "README.*.jinja", ) +DEV_DEPS = ( + "ruff>=0.15.10", + "mypy>=1.20.1", + "pre-commit>=4.5.1", + "pytest>=9.0.3", + "pytest-cov>=7.1.0", + "pytest-mock>=3.15.1", +) -def main(): - # {% if cookiecutter.packaging == 'poetry' %} - update_poetry_dependencies() - # {% endif %} - # {% if cookiecutter.packaging == 'uv' %} - update_uv_dependencies() - # {% endif %} - remove_unneeded_files() - +# mkdocs v2 will include breaking changes. +# See: https://github.com/ImperialCollegeLondon/python-template/discussions/530 +DOC_DEPS = ( + "mkdocs>=1.6.1,<2.0.0", + "mkdocstrings>=1.0.3", + "mkdocstrings-python>=2.0.3", + "mkdocs-material>=9.7.6", + "mkdocs-gen-files>=0.6.1", + "mkdocs-literate-nav>=0.6.3", + "mkdocs-section-index>=0.3.11", +) -def read_package_versions(*filenames: str) -> dict[str, str]: - """Read package versions from *requirements.txt.""" - regex = re.compile(r"^([^# ]+)==(.+)$") - packages: dict[str, str] = {} - for filename in filenames: - with open(filename) as f: - for line in f.readlines(): - if match := regex.match(line): - name = match.group(1).lower() - version = match.group(2) - packages[name] = version - return packages +MKDOCS_ENABLED = "{{ cookiecutter.mkdocs }}" == "True" -def update_poetry_dependencies(pyproject_path: str = "pyproject.toml"): - """Patch the versions of packages in pyproject.toml.""" - packages = read_package_versions( - "doc-requirements.txt", "dev-requirements.txt", "requirements.txt" - ) +def main(): + check_uv_installed() + add_uv_dependencies() + remove_unneeded_files() - output = "" - regex = re.compile(r'^([^ ]+) = "VERSION"$') - with open(pyproject_path) as f: - for line in f.readlines(): - if match := regex.match(line): - name = match.group(1) - output += f'{name} = "^{packages[name.lower()]}"\n' - else: - output += line - with open(pyproject_path, "w") as f: - f.write(output) +def check_uv_installed(): + try: + subprocess.run(["uv", "--version"], check=True, stdout=subprocess.DEVNULL) + except FileNotFoundError: + print( + "Error: 'uv' command not found. Please install 'uv' to use this template." + ) + exit(1) -def update_uv_dependencies(pyproject_path: str = "pyproject.toml"): - """Patch the versions of packages in pyproject.toml.""" - packages = read_package_versions( - "doc-requirements.txt", "dev-requirements.txt", "requirements.txt" - ) - output = "" - regex = re.compile(r'"([A-Za-z0-9_\-]+)\s*([=<>!~]+)\s*VERSION\s*(.*)"') - with open(pyproject_path) as f: - for line in f.readlines(): - if match := regex.match(line.strip()): - name = match.group(1) - operator = match.group(2) - version = packages[name.lower()] - trailing = match.group(3) - comma = "," if line.strip().endswith(",") else "" - output += f' "{name}{operator}{version}{trailing}"{comma}\n' - else: - output += line - with open(pyproject_path, "w") as f: - f.write(output) +def add_uv_dependencies(): + subprocess.run(["git", "init"], check=True) + subprocess.run(["uv", "add", "--dev", *DEV_DEPS], check=True) + if MKDOCS_ENABLED: + subprocess.run(["uv", "add", "--group", "doc", *DOC_DEPS], check=True) def remove_unneeded_files(): diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..0b895de --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,13 @@ +[project] +name = "python-template" +version = "0.1.0" +requires-python = ">=3.14" + +[dependency-groups] +dev = [ + "cookiecutter>=2.7.1", + "pytest>=9.0.3", +] + +[tool.uv] +package = false diff --git a/tests/test_pyproject_patching.py b/tests/test_pyproject_patching.py index a72f007..7dbc15d 100644 --- a/tests/test_pyproject_patching.py +++ b/tests/test_pyproject_patching.py @@ -1,80 +1,55 @@ -from hooks.post_gen_project import read_package_versions, update_poetry_dependencies -from pathlib import Path -from unittest.mock import patch, Mock - - -def test_read_package_versions(tmp_path: Path): - REQUIREMENTS1 = """ -# This is a comment -package_a==1.2.3 -package_b==4.5.6 -""" - path1 = tmp_path / "requirements1.txt" - with path1.open("w") as f: - f.write(REQUIREMENTS1) - - # Single file - assert read_package_versions(str(path1)) == { - "package_a": "1.2.3", - "package_b": "4.5.6", - } - - REQUIREMENTS2 = """ -package_b==1.2.3 -# Another comment -package_c==4.5.6 -""" - path2 = tmp_path / "requirements2.txt" - with path2.open("w") as f: - f.write(REQUIREMENTS2) - - # Multiple files - assert read_package_versions(str(path1), str(path2)) == { - "package_a": "1.2.3", - "package_b": "1.2.3", - "package_c": "4.5.6", - } - - REQUIREMENTS3 = "package_B==1.2.3" - path3 = tmp_path / "requirements3.txt" - with path3.open("w") as f: - f.write(REQUIREMENTS3) - - # Package names should be converted to lowercase - assert read_package_versions(str(path1), str(path3)) == { - "package_a": "1.2.3", - "package_b": "1.2.3", - } - - -PYPROJECT = """[tool.poetry] -name = "my_project" -authors = [ - "Jane Doe ", -] - -[tool.poetry.dependencies] -python = "^3.14" -package_a = "{package_a}" - -[tool.poetry.group.dev.dependencies] -package_b = "{package_b}" - -[tool.mypy] -disallow_any_explicit = true - -def test_update_poetry_dependencies(tmp_path: Path): -""" - - -@patch("hooks.post_gen_project.read_package_versions") -def test_update_poetry_dependencies(read_versions_mock: Mock, tmp_path: Path): - pyproject_path = tmp_path / "pyproject.toml" - with pyproject_path.open("w") as f: - f.write(PYPROJECT.format(package_a="VERSION", package_b="VERSION")) - - read_versions_mock.return_value = {"package_a": "1.2.3", "package_b": "4.5.6"} - update_poetry_dependencies(str(pyproject_path)) - with pyproject_path.open() as f: - content = f.read() - assert content == PYPROJECT.format(package_a="^1.2.3", package_b="^4.5.6") +from unittest.mock import patch + +from hooks import post_gen_project + + +def test_check_uv_installed_success(monkeypatch): + with patch("hooks.post_gen_project.subprocess.run") as mock_run: + post_gen_project.check_uv_installed() + mock_run.assert_called_once_with(["uv", "--version"], check=True, stdout=-3) + + +def test_check_uv_installed_failure(monkeypatch): + with patch("hooks.post_gen_project.subprocess.run", side_effect=FileNotFoundError): + with patch("builtins.print") as mock_print: + with patch("builtins.exit") as mock_exit: + post_gen_project.check_uv_installed() + mock_print.assert_called_once_with( + "Error: 'uv' command not found. Please install 'uv' to use this template." + ) + mock_exit.assert_called_once_with(1) + + +def test_add_uv_dependencies_dev_only(monkeypatch): + monkeypatch.setattr(post_gen_project, "MKDOCS_ENABLED", False) + with patch("hooks.post_gen_project.subprocess.run") as mock_run: + post_gen_project.add_uv_dependencies() + assert mock_run.call_count == 2 + call = mock_run.call_args_list[1] + assert call.args[0] == ["uv", "add", "--dev", *post_gen_project.DEV_DEPS] + assert call.kwargs["check"] is True + + +def test_add_uv_dependencies_includes_doc_group_when_mkdocs_enabled(monkeypatch): + monkeypatch.setattr(post_gen_project, "MKDOCS_ENABLED", True) + with patch("hooks.post_gen_project.subprocess.run") as mock_run: + post_gen_project.add_uv_dependencies() + assert mock_run.call_count == 3 + assert mock_run.call_args_list[1].args[0] == [ + "uv", + "add", + "--dev", + *post_gen_project.DEV_DEPS, + ] + assert mock_run.call_args_list[2].args[0] == [ + "uv", + "add", + "--group", + "doc", + *post_gen_project.DOC_DEPS, + ] + + +def test_mkdocs_constraint_preserves_v2_upper_bound(): + mkdocs_pin = next(d for d in post_gen_project.DOC_DEPS if d.startswith("mkdocs>")) + assert "<2.0.0" in mkdocs_pin diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..eecf4d1 --- /dev/null +++ b/uv.lock @@ -0,0 +1,372 @@ +version = 1 +revision = 3 +requires-python = ">=3.14" + +[[package]] +name = "arrow" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/33/032cdc44182491aa708d06a68b62434140d8c50820a087fac7af37703357/arrow-1.4.0.tar.gz", hash = "sha256:ed0cc050e98001b8779e84d461b0098c4ac597e88704a655582b21d116e526d7", size = 152931, upload-time = "2025-10-18T17:46:46.761Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl", hash = "sha256:749f0769958ebdc79c173ff0b0670d59051a535fa26e8eba02953dc19eb43205", size = 68797, upload-time = "2025-10-18T17:46:45.663Z" }, +] + +[[package]] +name = "binaryornot" +version = "0.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/86/72/4755b85101f37707c71526a301c1203e413c715a0016ecb592de3d2dcfff/binaryornot-0.6.0.tar.gz", hash = "sha256:cc8d57cfa71d74ff8c28a7726734d53a851d02fad9e3a5581fb807f989f702f0", size = 478718, upload-time = "2026-03-08T16:26:28.804Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/0c/31cfaa6b56fe23488ecb993bc9fc526c0d84d89607decdf2a10776426c2e/binaryornot-0.6.0-py3-none-any.whl", hash = "sha256:900adfd5e1b821255ba7e63139b0396b14c88b9286e74e03b6f51e0200331337", size = 14185, upload-time = "2026-03-08T16:26:27.466Z" }, +] + +[[package]] +name = "certifi" +version = "2026.4.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/25/ee/6caf7a40c36a1220410afe15a1cc64993a1f864871f698c0f93acb72842a/certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580", size = 137077, upload-time = "2026-04-22T11:26:11.191Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/30/7cd8fdcdfbc5b869528b079bfb76dcdf6056b1a2097a662e5e8c04f42965/certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a", size = 135707, upload-time = "2026-04-22T11:26:09.372Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" }, + { url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" }, + { url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" }, + { url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" }, + { url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" }, + { url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" }, + { url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" }, + { url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" }, + { url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" }, + { url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" }, + { url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" }, + { url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" }, + { url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" }, + { url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" }, + { url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" }, + { url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" }, + { url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" }, + { url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" }, + { url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" }, + { url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" }, + { url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" }, + { url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" }, + { url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" }, + { url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" }, + { url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" }, + { url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" }, +] + +[[package]] +name = "click" +version = "8.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/23/e4/796662cd90cf80e3a363c99db2b88e0e394b988a575f60a17e16440cd011/click-8.4.0.tar.gz", hash = "sha256:638f1338fe1235c8f4e008e4a8a254fb5c5fbdcbb40ece3c9142ebb78e792973", size = 350843, upload-time = "2026-05-17T00:47:58.425Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/ae/8e92f8058baf87f6c7d86ee7e457668690195cc77efedb8d3797a06e3940/click-8.4.0-py3-none-any.whl", hash = "sha256:40c50b7c6c6adac2823d411041ec84f3f103f1b280d5e9ce0d7f998995832f81", size = 116147, upload-time = "2026-05-17T00:47:56.842Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +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" } +wheels = [ + { 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" }, +] + +[[package]] +name = "cookiecutter" +version = "2.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "arrow" }, + { name = "binaryornot" }, + { name = "click" }, + { name = "jinja2" }, + { name = "python-slugify" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "rich" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/92/03/f4c96d8fd4f5e8af0210bf896eb63927f35d3014a8e8f3bf9d2c43ad3332/cookiecutter-2.7.1.tar.gz", hash = "sha256:ca7bb7bc8c6ff441fbf53921b5537668000e38d56e28d763a1b73975c66c6138", size = 142854, upload-time = "2026-03-04T04:06:02.786Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/a9/8c855c14b401dc67d20739345295af5afce5e930a69600ab20f6cfa50b5c/cookiecutter-2.7.1-py3-none-any.whl", hash = "sha256:cee50defc1eaa7ad0071ee9b9893b746c1b3201b66bf4d3686d0f127c8ed6cf9", size = 41317, upload-time = "2026-03-04T04:06:01.221Z" }, +] + +[[package]] +name = "idna" +version = "3.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/77/7b3966d0b9d1d31a36ddf1746926a11dface89a83409bf1483f0237aa758/idna-3.15.tar.gz", hash = "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc", size = 199245, upload-time = "2026-05-12T22:45:57.011Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/23/408243171aa9aaba178d3e2559159c24c1171a641aa83b67bdd3394ead8e/idna-3.15-py3-none-any.whl", hash = "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8", size = 72340, upload-time = "2026-05-12T22:45:55.733Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687, upload-time = "2026-05-07T12:08:27.182Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +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" } +wheels = [ + { 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" }, +] + +[[package]] +name = "packaging" +version = "26.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +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" } +wheels = [ + { 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" }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-slugify" +version = "8.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "text-unidecode" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/87/c7/5e1547c44e31da50a460df93af11a535ace568ef89d7a811069ead340c4a/python-slugify-8.0.4.tar.gz", hash = "sha256:59202371d1d05b54a9e7720c5e038f928f45daaffe41dd10822f3907b937c856", size = 10921, upload-time = "2024-02-08T18:32:45.488Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/62/02da182e544a51a5c3ccf4b03ab79df279f9c60c5e82d5e8bec7ca26ac11/python_slugify-8.0.4-py2.py3-none-any.whl", hash = "sha256:276540b79961052b66b7d116620b36518847f52d5fd9e3a70164fc8c50faa6b8", size = 10051, upload-time = "2024-02-08T18:32:43.911Z" }, +] + +[[package]] +name = "python-template" +version = "0.1.0" +source = { virtual = "." } + +[package.dev-dependencies] +dev = [ + { name = "cookiecutter" }, + { name = "pytest" }, +] + +[package.metadata] + +[package.metadata.requires-dev] +dev = [ + { name = "cookiecutter", specifier = ">=2.7.1" }, + { name = "pytest", specifier = ">=9.0.3" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "requests" +version = "2.34.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, +] + +[[package]] +name = "rich" +version = "15.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "text-unidecode" +version = "1.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ab/e2/e9a00f0ccb71718418230718b3d900e71a5d16e701a3dae079a21e9cd8f8/text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93", size = 76885, upload-time = "2019-08-30T21:36:45.405Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/a5/c0b6468d3824fe3fde30dbb5e1f687b291608f9473681bbf7dabbf5a87d7/text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8", size = 78154, upload-time = "2019-08-30T21:37:03.543Z" }, +] + +[[package]] +name = "tzdata" +version = "2026.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/19/1b9b0e29f30c6d35cb345486df41110984ea67ae69dddbc0e8a100999493/tzdata-2026.2.tar.gz", hash = "sha256:9173fde7d80d9018e02a662e168e5a2d04f87c41ea174b139fbef642eda62d10", size = 198254, upload-time = "2026-04-24T15:22:08.651Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/e4/dccd7f47c4b64213ac01ef921a1337ee6e30e8c6466046018326977efd95/tzdata-2026.2-py2.py3-none-any.whl", hash = "sha256:bbe9af844f658da81a5f95019480da3a89415801f6cc966806612cc7169bffe7", size = 349321, upload-time = "2026-04-24T15:22:05.876Z" }, +] + +[[package]] +name = "urllib3" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, +] diff --git a/{{ cookiecutter.project_slug }}/.github/workflows/ci.yml b/{{ cookiecutter.project_slug }}/.github/workflows/ci.yml index b16c8b3..8bed23d 100644 --- a/{{ cookiecutter.project_slug }}/.github/workflows/ci.yml +++ b/{{ cookiecutter.project_slug }}/.github/workflows/ci.yml @@ -26,47 +26,12 @@ jobs: steps: - uses: actions/checkout@v4 -{%- if cookiecutter.packaging == "poetry" %} - - name: Install poetry - run: pipx install poetry -{%- elif cookiecutter.packaging == "uv" %} - uses: astral-sh/setup-uv@v7 with: enable-cache: true prune-cache: false activate-environment: true python-version: ${{ "{{" }} matrix.python-version {{ "}}" }} -{%- endif %} - -{%- if cookiecutter.packaging != "uv" %} - - uses: actions/setup-python@v5 - with: - python-version: ${{ "{{" }}matrix.python-version{{ "}}" }} - cache: {% if cookiecutter.packaging == "poetry" %}poetry{% elif cookiecutter.packaging == "pip-tools" %}pip{% endif %} -{%- endif %} - -{%- if cookiecutter.packaging == "poetry" %} - - name: Install dependencies - run: poetry install - - - name: Run mypy - run: poetry run mypy . - - - name: Run tests - run: poetry run pytest -{%- elif cookiecutter.packaging == "pip-tools" %} - - name: Install dependencies - run: | - pip install --upgrade pip - pip install -r dev-requirements.txt - pip install -e . - - - name: Run mypy - run: mypy . - - - name: Run tests - run: pytest -{%- elif cookiecutter.packaging == "uv" %} - name: Install dependencies run: uv sync @@ -76,20 +41,12 @@ jobs: - name: Run tests run: pytest - -{%- endif %} {%- if cookiecutter.mkdocs %} -{%- if cookiecutter.packaging == "uv" %} - name: Install doc dependencies run: uv sync --group doc -{%- elif cookiecutter.packaging == "pip-tools" %} - - - name: Install doc dependencies - run: pip install -r doc-requirements.txt -{%- endif %} - name: Check that documentation builds cleanly with MkDocs if: matrix.os == 'ubuntu-latest' - run: {% if cookiecutter.packaging == "poetry" %}poetry run mkdocs build --strict{% else %}mkdocs build --strict{% endif %} + run: mkdocs build --strict {%- endif %} diff --git a/{{ cookiecutter.project_slug }}/.github/workflows/docs.yml b/{{ cookiecutter.project_slug }}/.github/workflows/docs.yml index faa2624..8ab8f17 100644 --- a/{{ cookiecutter.project_slug }}/.github/workflows/docs.yml +++ b/{{ cookiecutter.project_slug }}/.github/workflows/docs.yml @@ -31,27 +31,6 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 -{%- if cookiecutter.packaging == 'poetry' %} - - name: Install Poetry - run: pipx install poetry - - uses: actions/setup-python@v5 - with: - python-version: '3.14' - cache: poetry - - name: Install dependencies - run: poetry install - - name: Build docs with MkDocs - run: poetry run mkdocs build --strict -{%- elif cookiecutter.packaging == 'pip-tools' %} - - uses: actions/setup-python@v5 - with: - python-version: '3.14' - cache: pip - - name: Install dependencies - run: pip install -r doc-requirements.txt - - name: Build docs with MkDocs - run: mkdocs build --strict -{%- elif cookiecutter.packaging == 'uv' %} - name: Install uv uses: astral-sh/setup-uv@v7 with: @@ -65,7 +44,6 @@ jobs: run: uv sync --group doc - name: Build docs with MkDocs run: uv run mkdocs build --strict -{%- endif %} - name: Setup Pages uses: actions/configure-pages@v5 - name: Upload artifact diff --git a/{{ cookiecutter.project_slug }}/README.pip-tools.jinja b/{{ cookiecutter.project_slug }}/README.pip-tools.jinja deleted file mode 100644 index 57abb97..0000000 --- a/{{ cookiecutter.project_slug }}/README.pip-tools.jinja +++ /dev/null @@ -1,120 +0,0 @@ -This is a Python application that uses [`pip-tools`][pip-tools] for packaging and -dependency management. It provides [`pre-commit`][pre-commit] hooks for various linters -and formatters and automated tests using [`pytest`][pytest] and [GitHub Actions]. -Pre-commit hooks are automatically kept updated with a dedicated GitHub Action, this can -be removed and replaced with [pre-commit.ci] if using a public repo. The package version -is dynamically generated from the most recent git tag using -[`setuptools-scm`][setuptools-scm]. - -[`pip-tools`][pip-tools] is chosen as a lightweight dependency manager that adheres to -the [latest standards] using `pyproject.toml`. - -It was developed by the [Imperial College Research Software Engineering Team]. - -## Usage - -To get started: - -1. Activate a git repository (required for `pre-commit` and the package versioning with -`setuptools-scm`): - - ```bash - git init - ``` - -1. Create and activate a [virtual environment]: - - ```bash - python -m venv .venv - source .venv/bin/activate # with Powershell on Windows: `.venv\Scripts\Activate.ps1` - ``` - -1. Install development requirements and the package in editable mode: - - ```bash - pip install -r dev-requirements.txt - pip install -e . - ``` -{% if cookiecutter.mkdocs %} -1. (Optionally) install tools for building documentation: - - ```bash - pip install -r doc-requirements.txt - ``` -{% endif %} -1. Install the pre-commit git hooks: - - ```bash - pre-commit install - ``` - -1. Update the pre-commit hooks - - ```bash - pre-commit autoupdate - ``` - -1. Run the main app: - - ```bash - python -m {{ cookiecutter.project_slug }} - ``` - -1. Run the tests: - - ```bash - pytest - ``` - -1. Create an initial commit (it's possible there might be some failures in pre-commit): - - ```bash - git add . - git commit -m "Initial commit" - ``` - -## Updating Dependencies - -To add or remove dependencies: - -1. Edit the `dependencies` variables in the `pyproject.toml` file (aim to keep -development tools separate from the project requirements). -1. Update the requirements files: - - `pip-compile` for `requirements.txt` - the project requirements. - - `pip-compile --extra dev -o dev-requirements.txt` for the development requirements. -{% if cookiecutter.mkdocs %} - `pip-compile --extra doc -o doc-requirements.txt` for -the documentation tools. -{% endif %}1. Sync the files with your installation (install packages): - - `pip-sync *requirements.txt` - -To upgrade pinned versions, use the `--upgrade` flag with `pip-compile`. - -Versions can be restricted from updating within the `pyproject.toml` using standard -python package version specifiers, i.e. `"black<23"` or `"pip-tools!=6.12.2"` - -## Customising - -All configuration can be customised to your preferences. The key places to make changes -for this are: - -- The `pyproject.toml` file, where you can edit: - - The build system (change from setuptools to other packaging tools like [Hatch] or -[flit]). - - The python version. - - The project dependencies. Extra optional dependencies can be added by adding -another list under `[project.optional-dependencies]` (i.e. `doc = ["mkdocs"]`). - - The `mypy` and `pytest` configurations. -- The `.pre-commit-config.yaml` for pre-commit settings. -- The `.github` directory for all the CI configuration. - -[pip-tools]: https://pip-tools.readthedocs.io/en/stable/ -[pre-commit]: https://pre-commit.com/ -[pytest]: https://pytest.org/ -[GitHub Actions]: https://github.com/features/actions -[pre-commit.ci]: https://pre-commit.ci -[setuptools-scm]: https://setuptools-scm.readthedocs.io/en/latest/ -[latest standards]: https://peps.python.org/pep-0621/ -[Imperial College Research Software Engineering Team]: https://www.imperial.ac.uk/admin-services/ict/self-service/research-support/rcs/service-offering/research-software-engineering/ -[virtual environment]: https://docs.python.org/3/library/venv.html -[Hatch]: https://hatch.pypa.io/ -[flit]: https://flit.pypa.io/ diff --git a/{{ cookiecutter.project_slug }}/README.poetry.jinja b/{{ cookiecutter.project_slug }}/README.poetry.jinja deleted file mode 100644 index d9724ee..0000000 --- a/{{ cookiecutter.project_slug }}/README.poetry.jinja +++ /dev/null @@ -1,76 +0,0 @@ -This is a Python application that uses [Poetry] for packaging and dependency management. -It provides [`pre-commit`][pre-commit] hooks for various linters -and formatters and automated tests using [`pytest`][pytest] and [GitHub Actions]. -Pre-commit hooks are automatically kept updated with a dedicated GitHub Action, this can -be removed and replaced with [pre-commit.ci] if using a public repo. - -It was developed by the [Imperial College Research Software Engineering Team]. - -## Usage - -To get started: - -1. [Download and install Poetry] following the instructions for your OS. -1. Activate a git repository (required for `pre-commit`): - - ```bash - git init - ``` - -1. Set up the virtual environment: - - ```bash - poetry install - ``` - -1. Activate the virtual environment (see [Poetry docs][poetry activate] for more info): - - ```bash - eval $(poetry env activate) # bash/Zsh/Csh - eval (poetry env activate) # Fish - Invoke-Expression (poetry env activate) # Powershell - ``` - -1. Install the pre-commit git hooks: - - ```bash - pre-commit install - ``` - -1. Update the pre-commit hooks - - ```bash - pre-commit autoupdate - ``` - -1. Run the main app: - - ```bash - python -m {{ cookiecutter.project_slug }} - ``` - -1. Run the tests: - - ```bash - pytest - ``` - -1. Create an initial commit (it's possible there might be some failures in pre-commit): - - ```bash - git add . - git commit -m "Initial commit" - ``` - -## Updating Dependencies - -See the [Poetry] docs for managing dependencies. - -[Poetry]: https://python-poetry.org -[pre-commit]: https://pre-commit.com/ -[pytest]: https://pytest.org/ -[GitHub Actions]: https://github.com/features/actions -[pre-commit.ci]: https://pre-commit.ci -[Imperial College Research Software Engineering Team]: https://www.imperial.ac.uk/admin-services/ict/self-service/research-support/rcs/service-offering/research-software-engineering/ -[Download and install Poetry]: https://python-poetry.org/docs/#installation -[poetry activate]: https://python-poetry.org/docs/managing-environments/#activating-the-environment diff --git a/{{ cookiecutter.project_slug }}/dev-requirements.txt b/{{ cookiecutter.project_slug }}/dev-requirements.txt deleted file mode 100644 index ffa224b..0000000 --- a/{{ cookiecutter.project_slug }}/dev-requirements.txt +++ /dev/null @@ -1,67 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.12 -# by the following command: -# -# pip-compile --extra=dev --output-file=dev-requirements.txt pyproject.toml -# -build==1.4.3 - # via pip-tools -cfgv==3.5.0 - # via pre-commit -click==8.3.2 - # via pip-tools -coverage[toml]==7.13.5 - # via pytest-cov -distlib==0.4.0 - # via virtualenv -filelock==3.25.2 - # via virtualenv -identify==2.6.18 - # via pre-commit -iniconfig==2.3.0 - # via pytest -mypy==1.20.1 - # via {{ cookiecutter.project_slug }} (pyproject.toml) -mypy-extensions==1.1.0 - # via mypy -nodeenv==1.10.0 - # via pre-commit -packaging==26.0 - # via - # build - # pytest -pip-tools==7.5.3 - # via {{ cookiecutter.project_slug }} (pyproject.toml) -platformdirs==4.9.6 - # via virtualenv -pluggy==1.6.0 - # via pytest -pre-commit==4.5.1 - # via {{ cookiecutter.project_slug }} (pyproject.toml) -pyproject-hooks==1.2.0 - # via - # build - # pip-tools -pytest==9.0.3 - # via - # {{ cookiecutter.project_slug }} (pyproject.toml) - # pytest-cov - # pytest-mock -pytest-cov==7.1.0 - # via {{ cookiecutter.project_slug }} (pyproject.toml) -pytest-mock==3.15.1 - # via {{ cookiecutter.project_slug }} (pyproject.toml) -pyyaml==6.0.3 - # via pre-commit -ruff==0.15.10 - # via {{ cookiecutter.project_slug }} (pyproject.toml) -typing-extensions==4.15.0 - # via mypy -virtualenv==21.2.3 - # via pre-commit -wheel==0.46.3 - # via pip-tools - -# The following packages are considered to be unsafe in a requirements file: -# pip -# setuptools diff --git a/{{ cookiecutter.project_slug }}/doc-requirements.txt b/{{ cookiecutter.project_slug }}/doc-requirements.txt deleted file mode 100644 index 8609c76..0000000 --- a/{{ cookiecutter.project_slug }}/doc-requirements.txt +++ /dev/null @@ -1,113 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.12 -# by the following command: -# -# pip-compile --extra=doc --output-file=doc-requirements.txt pyproject.toml -# -babel==2.18.0 - # via mkdocs-material -certifi==2026.2.25 - # via requests -charset-normalizer==3.4.7 - # via requests -click==8.3.2 - # via - # mkdocs - # mkdocstrings -colorama==0.4.6 - # via - # griffe - # mkdocs-material -ghp-import==2.1.0 - # via mkdocs -griffe==2.0.2 - # via mkdocstrings-python -idna==3.11 - # via requests -jinja2==3.1.6 - # via - # mkdocs - # mkdocs-material - # mkdocstrings -markdown==3.10.2 - # via - # mkdocs - # mkdocs-autorefs - # mkdocs-material - # mkdocstrings - # pymdown-extensions -markupsafe==3.0.3 - # via - # jinja2 - # mkdocs - # mkdocs-autorefs - # mkdocstrings -mergedeep==1.3.4 - # via - # mkdocs - # mkdocs-get-deps -mkdocs==1.6.1 - # via - # mkdocs-autorefs - # mkdocs-gen-files - # mkdocs-literate-nav - # mkdocs-material - # mkdocs-section-index - # mkdocstrings - # {{ cookiecutter.project_slug }} (pyproject.toml) -mkdocs-autorefs==1.4.4 - # via mkdocstrings -mkdocs-gen-files==0.6.1 - # via {{ cookiecutter.project_slug }} (pyproject.toml) -mkdocs-get-deps==0.2.2 - # via mkdocs -mkdocs-literate-nav==0.6.3 - # via {{ cookiecutter.project_slug }} (pyproject.toml) -mkdocs-material==9.7.6 - # via {{ cookiecutter.project_slug }} (pyproject.toml) -mkdocs-material-extensions==1.3.1 - # via mkdocs-material -mkdocs-section-index==0.3.11 - # via {{ cookiecutter.project_slug }} (pyproject.toml) -mkdocstrings==1.0.3 - # via - # mkdocstrings-python - # {{ cookiecutter.project_slug }} (pyproject.toml) -mkdocstrings-python==2.0.3 - # via {{ cookiecutter.project_slug }} (pyproject.toml) -packaging==26.0 - # via mkdocs -paginate==0.5.7 - # via mkdocs-material -pathspec==1.0.4 - # via mkdocs -platformdirs==4.9.6 - # via - # mkdocs-get-deps - # mkdocstrings -pygments==2.20.0 - # via mkdocs-material -pymdown-extensions==10.21.2 - # via - # mkdocs-material - # mkdocstrings -python-dateutil==2.9.0.post0 - # via ghp-import -pyyaml==6.0.3 - # via - # mkdocs - # mkdocs-get-deps - # pymdown-extensions - # pyyaml-env-tag -pyyaml-env-tag==1.1 - # via mkdocs -regex==2026.4.4 - # via mkdocs-material -requests==2.33.1 - # via mkdocs-material -six==1.17.0 - # via python-dateutil -urllib3==2.6.3 - # via requests -watchdog==6.0.0 - # via mkdocs diff --git a/{{ cookiecutter.project_slug }}/pyproject.toml b/{{ cookiecutter.project_slug }}/pyproject.toml index 4a2c334..290e5d9 100644 --- a/{{ cookiecutter.project_slug }}/pyproject.toml +++ b/{{ cookiecutter.project_slug }}/pyproject.toml @@ -1,10 +1,6 @@ [project] name = "{{ cookiecutter.project_slug }}" -{%- if cookiecutter.packaging == "poetry"%} -version = "0.1.0" -{%- else %} dynamic = ["version"] -{%- endif %} description = "{{ cookiecutter.project_description }}" authors = [ {name = "{{ cookiecutter.author }}", email = "{{ cookiecutter.author_email }}"} @@ -13,79 +9,12 @@ authors = [ {%- endif %} ] {%- if cookiecutter.use_bsd3_license %} -{%- if cookiecutter.packaging != "poetry" %} license = "BSD-3-Clause" license-files = ["LICENSE"] -{%- else %} -license = {text = "BSD-3-Clause"} -{%- endif %} {%- endif %} -{%- if cookiecutter.packaging != "poetry" %} requires-python = ">=3.14" dependencies = [] -{% if cookiecutter.packaging == "uv" %} -[dependency-groups] -dev = [ - "ruff>=VERSION", - "mypy>=VERSION", - "pre-commit>=VERSION", - "pytest>=VERSION", - "pytest-cov>=VERSION", - "pytest-mock>=VERSION", -] -{% if cookiecutter.mkdocs -%} -doc = [ - # NB: mkdocs v2 will include breaking changes. - # See: https://github.com/ImperialCollegeLondon/python-template/discussions/530 - "mkdocs>=VERSION,<2.0.0", - "mkdocstrings>=VERSION", - "mkdocstrings-python>=VERSION", - "mkdocs-material>=VERSION", - "mkdocs-gen-files>=VERSION", - "mkdocs-literate-nav>=VERSION", - "mkdocs-section-index>=VERSION", -] -{%- endif %} -{%- else %} -[project.optional-dependencies] -dev = ["ruff", "mypy", "pip-tools", "pre-commit", "pytest", "pytest-cov", "pytest-mock"] -{% if cookiecutter.mkdocs -%} -doc = [ - "mkdocs", - "mkdocstrings", - "mkdocstrings-python", - "mkdocs-material", - "mkdocs-gen-files", - "mkdocs-literate-nav", - "mkdocs-section-index", -] -{%- endif %} -{%- endif %} -{%- endif %} -{%- if cookiecutter.packaging == "poetry" %} - -[tool.poetry.dependencies] -python = "^3.14" - -[tool.poetry.group.dev.dependencies] -mypy = "VERSION" -pytest = "VERSION" -pytest-cov = "VERSION" -pytest-mock = "VERSION" -pre-commit = "VERSION" -ruff = "VERSION" -{% if cookiecutter.mkdocs %} -[tool.poetry.group.docs.dependencies] -mkdocs = "VERSION" -mkdocstrings = "VERSION" -mkdocstrings-python = "VERSION" -mkdocs-material = "VERSION" -mkdocs-gen-files = "VERSION" -mkdocs-literate-nav = "VERSION" -mkdocs-section-index = "VERSION" -{%- endif -%} -{%- endif %} [tool.mypy] disallow_any_explicit = true @@ -121,7 +50,6 @@ pydocstyle.convention = "google" "D100", # Missing docstring in public module "D104", # Missing docstring in public package ] -{% if cookiecutter.packaging != "poetry" %} [build-system] requires = ["setuptools>=78", "setuptools-scm"] build-backend = "setuptools.build_meta" @@ -130,8 +58,3 @@ build-backend = "setuptools.build_meta" packages = ["{{ cookiecutter.project_slug }}"] [tool.setuptools_scm] -{%- else %} -[build-system] -requires = ["poetry-core>=2.0.0,<3.0.0"] -build-backend = "poetry.core.masonry.api" -{%- endif %} diff --git a/{{ cookiecutter.project_slug }}/requirements.txt b/{{ cookiecutter.project_slug }}/requirements.txt deleted file mode 100644 index f8538a1..0000000 --- a/{{ cookiecutter.project_slug }}/requirements.txt +++ /dev/null @@ -1,6 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.12 -# by the following command: -# -# pip-compile -#