From 8fd57fdf4c48628aa33cd68835f0720119fe31f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9d=C3=A9ric=20Hurier=20=28Fmind=29?= Date: Sun, 15 Dec 2024 12:59:16 +0100 Subject: [PATCH 1/5] feat(upgrade): upgrade to v3 of the mlops-python-package, replacing poetry with uv BREAKING CHANGE: --- .github/FUNDING.yml | 1 - .github/workflows/tests.yml | 16 +- .gitignore | 3 - .pre-commit-config.yaml | 8 +- LICENSE.txt | 2 - README.md | 11 +- cookiecutter-mlops-package.code-workspace | 3 +- cookiecutter.json | 2 +- poetry.toml | 5 - pyproject.toml | 49 +- tasks.py | 17 +- tests/test_cookiecutter.py | 23 +- uv.lock | 681 ++++++++++++++++++ .../.github/actions/setup/action.yml | 12 +- .../.github/workflows/check.yml | 10 +- .../.github/workflows/publish.yml | 8 +- .../.pre-commit-config.yaml | 12 +- {{cookiecutter.repository}}/Dockerfile | 4 +- .../{LICENCE.txt => LICENSE.txt} | 0 {{cookiecutter.repository}}/MLproject | 4 +- {{cookiecutter.repository}}/README.md | 6 +- .../confs/{.gitkeep => inference.yaml} | 0 .../confs/training.yaml | 0 {{cookiecutter.repository}}/confs/tuning.yaml | 0 {{cookiecutter.repository}}/poetry.toml | 5 - {{cookiecutter.repository}}/pyproject.toml | 84 +-- {{cookiecutter.repository}}/tasks/__init__.py | 2 +- {{cookiecutter.repository}}/tasks/checks.py | 22 +- {{cookiecutter.repository}}/tasks/cleans.py | 10 +- {{cookiecutter.repository}}/tasks/commits.py | 8 +- .../tasks/containers.py | 2 +- {{cookiecutter.repository}}/tasks/docs.py | 6 +- {{cookiecutter.repository}}/tasks/formats.py | 6 +- {{cookiecutter.repository}}/tasks/installs.py | 14 +- {{cookiecutter.repository}}/tasks/mlflow.py | 13 +- {{cookiecutter.repository}}/tasks/packages.py | 10 +- {{cookiecutter.repository}}/tasks/projects.py | 34 +- .../tests/test_scripts.py | 1 + 38 files changed, 892 insertions(+), 202 deletions(-) delete mode 100644 poetry.toml create mode 100644 uv.lock rename {{cookiecutter.repository}}/{LICENCE.txt => LICENSE.txt} (100%) rename {{cookiecutter.repository}}/confs/{.gitkeep => inference.yaml} (100%) create mode 100644 {{cookiecutter.repository}}/confs/training.yaml create mode 100644 {{cookiecutter.repository}}/confs/tuning.yaml delete mode 100644 {{cookiecutter.repository}}/poetry.toml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 0233097..fa0bf3e 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,4 +1,3 @@ # These are supported funding model platforms -# github: ["MLOps-Courses"] custom: ["https://donate.stripe.com/4gw8xT9oVbCc98s7ss"] diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 982ee72..253b206 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -2,7 +2,7 @@ name: Tests on: pull_request: branches: - - main + - '*' permissions: contents: read id-token: write @@ -15,9 +15,13 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 - - name: Install - run: | - pipx install poetry - poetry install + - name: Install uv + uses: astral-sh/setup-uv@v4 + with: + enable-cache: true + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version-file: .python-version - name: Test - run: poetry run pytest tests/ + run: uv run invoke test diff --git a/.gitignore b/.gitignore index 28ea3ad..ed29e00 100644 --- a/.gitignore +++ b/.gitignore @@ -4,9 +4,6 @@ /.idea/ /.vscode/ -# Poetry -poetry.lock - # Python .venv/ *.py[cod] diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d57bce6..fd9cdf2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,10 +2,10 @@ # https://pre-commit.com/hooks.html default_language_version: - python: python3 + python: python3.12 repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 + rev: v5.0.0 hooks: - id: check-added-large-files - id: check-case-conflict @@ -13,7 +13,3 @@ repos: - id: end-of-file-fixer - id: mixed-line-ending - id: trailing-whitespace - - repo: https://github.com/python-poetry/poetry - rev: 1.8.3 - hooks: - - id: poetry-check diff --git a/LICENSE.txt b/LICENSE.txt index bbab95b..0e77b64 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,5 +1,3 @@ -Copyright 2024 Médéric HURIER (Fmind) - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. diff --git a/README.md b/README.md index a39c42a..92c8434 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# MLOps Package Cookiecutter +# Cookiecutter - MLOps Package [![Release](https://img.shields.io/github/v/release/fmind/cookiecutter-mlops-package)](https://github.com/fmind/cookiecutter-mlops-package/releases) [![License](https://img.shields.io/github/license/fmind/cookiecutter-mlops-package)](https://github.com/fmind/cookiecutter-mlops-package/blob/main/LICENSE.txt) @@ -10,19 +10,20 @@ The template provides a robust foundation for building, testing, packaging, and **Related resources**: - **[MLOps Coding Course (Learning)](https://mlops-coding-course.fmind.dev/)**: Learn how to create, develop, and maintain a state-of-the-art MLOps code base. - **[MLOps Python Package (Example)](https://github.com/fmind/mlops-python-package)**: Kickstart your MLOps initiative with a flexible, robust, and productive Python package. +- **[LLMOps Coding Package (Example)](https://github.com/callmesora/llmops-python-package/)**: Example with best practices and tools to support your LLMOps projects. ## Philosophy This [Cookiecutter](https://cookiecutter.readthedocs.io/) is designed to be a common ground for diverse MLOps environments. Whether you're working with [Kubernetes](https://www.kubeflow.org/), [Vertex AI](https://cloud.google.com/vertex-ai), [Databricks](https://www.databricks.com/), [Azure ML](https://azure.microsoft.com/en-us/products/machine-learning), or [AWS SageMaker](https://aws.amazon.com/sagemaker/), the core principles of using Python packages and Docker images remain consistent. -This template equips you with the essentials for creating, testing, and packaging your code, providing a solid base for integration into your chosen platform. To fully leverage its capabilities within a specific environment, you might need to combine it with external tools like [Airflow](https://airflow.apache.org/) for orchestration or platform-specific SDKs for deployment. +This template equips you with the essentials for creating, testing, and packaging your AI/ML code, providing a solid base for integration into your chosen MLOps platform. To fully leverage its capabilities within a specific environment, you might need to combine it with external tools like [Airflow](https://airflow.apache.org/) for orchestration or platform-specific SDKs for deployment. You have the freedom to structure your `src/` and `tests/` directories according to your preferences. Alternatively, you can draw inspiration from the structure used in the [MLOps Python Package](https://github.com/fmind/mlops-python-package) project for a ready-made implementation. ## Key Features * **Streamlined Project Structure:** A well-defined directory layout for source code, tests, documentation, tasks, and Docker configurations. -* **Poetry Integration:** Effortless dependency management and packaging with [Poetry](https://python-poetry.org/). +* **Uv Integration:** Effortless dependency management and packaging with [uv](https://docs.astral.sh/uv/). * **Automated Testing and Checks:** Pre-configured workflows using [Pytest](https://docs.pytest.org/), [Ruff](https://docs.astral.sh/ruff/), [Mypy](https://mypy.readthedocs.io/), [Bandit](https://bandit.readthedocs.io/), and [Coverage](https://coverage.readthedocs.io/) to ensure code quality, style, security, and type safety. * **Pre-commit Hooks:** Automatic code formatting and linting with [Ruff](https://docs.astral.sh/ruff/) and other pre-commit hooks to maintain consistency. * **MLflow Project Ready:** An MLproject file for executing jobs using [MLflow](https://mlflow.org/), allowing for easy experimentation and tracking. @@ -51,7 +52,7 @@ You'll be prompted for the following variables: - `version`: The initial version of your project. - `description`: A brief description of your project. - `python_version`: The Python version to use (e.g., 3.12). -- `mlflow_version`: The MLflow version to use (e.g., 2.14.3). +- `mlflow_version`: The MLflow version to use (e.g., 2.19.0). 2. **Initialize a git repository:** @@ -97,7 +98,7 @@ After installing dependencies and setting up MLflow: invoke projects ``` -This will execute the "main" job defined in your [`MLproject`](https://github.com/fmind/cookiecutter-mlops-package/blob/main/%7B%7Bcookiecutter.repository%7D%7D/MLproject) file. You can specify different jobs using the `-P job=your_job_name` flag. +This will execute the default jobs defined in your [`MLproject`](https://github.com/fmind/cookiecutter-mlops-package/blob/main/%7B%7Bcookiecutter.repository%7D%7D/MLproject) file. You can specify different jobs using the `-P job=your_job_name` flag. ### Building and Running Your Docker Image diff --git a/cookiecutter-mlops-package.code-workspace b/cookiecutter-mlops-package.code-workspace index af7ba7f..f673397 100644 --- a/cookiecutter-mlops-package.code-workspace +++ b/cookiecutter-mlops-package.code-workspace @@ -13,7 +13,8 @@ }, "extensions": { "recommendations": [ - "ms-python.python", + "dchanco.vsc-invoke", + "ms-python.python" ] } } diff --git a/cookiecutter.json b/cookiecutter.json index e466af4..5c941d2 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -7,7 +7,7 @@ "version": "0.1.0", "description": "TODO", "python_version": "3.12", - "mlflow_version": "2.14.3", + "mlflow_version": "2.19.0", "__prompts__": { "user": "GitHub User", "name": "Project Name", diff --git a/poetry.toml b/poetry.toml deleted file mode 100644 index 7783e1b..0000000 --- a/poetry.toml +++ /dev/null @@ -1,5 +0,0 @@ -# https://python-poetry.org/docs/configuration/ - -[virtualenvs] -in-project = true -prefer-active-python = true diff --git a/pyproject.toml b/pyproject.toml index 4df96b1..b471fd4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,45 +1,44 @@ -# https://python-poetry.org/docs/pyproject/ +# https://docs.astral.sh/uv/reference/settings/ +# https://packaging.python.org/en/latest/guides/writing-pyproject-toml/ -# PACKAGE +# PROJECT -[tool.poetry] +[project] name = "cookiecutter-mlops-package" version = "1.0.0" -description = "Build and deploy Python packages and Docker images for MLOps tasks." -repository = "https://github.com/fmind/cookiecutter-mlops-package" -authors = ["Médéric HURIER "] +description = "Build and deploy Python packages and Docker images for MLOps projects." +authors = [{ name = "Médéric HURIER", email = "github@fmind.dev" }] readme = "README.md" -license = "MIT" -package-mode = false +requires-python = ">=3.12" +license = { file = "LICENSE.txt" } # DEPENDENCIES -[tool.poetry.dependencies] -python = "^3.12" +[dependency-groups] +dev = [ + "commitizen>=4.1.0", + "invoke>=2.2.0", + "pre-commit>=4.0.1", + "pytest>=8.3.4", + "pytest-cookies>=0.7.0", + "pytest-shell-utilities>=1.9.7", +] -[tool.poetry.group.dev.dependencies] -commitizen = "^3.28.0" -invoke = "^2.2.0" -pre-commit = "^3.7.1" -pytest = "^8.3.2" -pytest-cookies = "^0.7.0" -pytest-shell-utilities = "^1.9.0" +# TOOLS -# CONFIGURATIONS +[tool.uv] +package = false + +[tool.bandit] +targets = ["src"] [tool.commitizen] name = "cz_conventional_commits" tag_format = "v$version" version_scheme = "pep440" -version_provider = "poetry" +version_provider = "pep621" update_changelog_on_bump = true [tool.pytest.ini_options] log_cli = true log_cli_level = "INFO" - -# SYSTEMS - -[build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" diff --git a/tasks.py b/tasks.py index 5460133..9db1ca7 100644 --- a/tasks.py +++ b/tasks.py @@ -7,19 +7,30 @@ # %% TASKS + +@task +def clean(ctx: Context) -> None: + """Clean the project.""" + ctx.run("rm -rf .venv/") + ctx.run("rm -rf .mypy_cache/") + ctx.run("rm -rf .pytest_cache/") + ctx.run("find . -type f -name '*.py[co]' -delete") + ctx.run(r"find . -type d -name __pycache__ -exec rm -r {} \+") + + @task def install(ctx: Context) -> None: """Install the project.""" - ctx.run("poetry install") + ctx.run("uv sync --all-groups") @task def hooks(ctx: Context) -> None: """Setup the project hooks.""" - ctx.run("poetry run pre-commit install") + ctx.run("uv run pre-commit install") @task def test(ctx: Context) -> None: """Run the project unit tests.""" - ctx.run("poetry run pytest tests/") + ctx.run("uv run pytest tests/") diff --git a/tests/test_cookiecutter.py b/tests/test_cookiecutter.py index 50df86d..9832aa2 100644 --- a/tests/test_cookiecutter.py +++ b/tests/test_cookiecutter.py @@ -9,14 +9,15 @@ COMMANDS = [ "git init", - "invoke cleans.reset", - "invoke installs", - "invoke formats", - "invoke checks", - "invoke docs", - "invoke projects", - "invoke packages", - "invoke containers", + "uv run invoke cleans.reset", + "uv run invoke installs", + "uv run invoke formats", + "uv run invoke checks", + "uv run invoke docs", + "uv run invoke projects", + "uv run invoke packages", + "uv run invoke containers", + "uv run invoke mlflow.doctor", ] # %% TESTS @@ -26,13 +27,13 @@ def test_project_generation(cookies: Cookies) -> None: """Test the generation of the project.""" # given context = { - "user": "test", + "user": "tester", "name": "MLOps 123", "license": "apache-2", "version": "1.0.0", - "description": "DONE", + "description": "A test project.", "python_version": "3.12", - "mlflow_version": "2.14.3", + "mlflow_version": "2.19.0", } repository = context['name'].lower().replace(' ', '-') package = repository.replace('-', '_') diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..ded24ca --- /dev/null +++ b/uv.lock @@ -0,0 +1,681 @@ +version = 1 +requires-python = ">=3.12" + +[[package]] +name = "argcomplete" +version = "3.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7f/03/581b1c29d88fffaa08abbced2e628c34dd92d32f1adaed7e42fc416938b0/argcomplete-3.5.2.tar.gz", hash = "sha256:23146ed7ac4403b70bd6026402468942ceba34a6732255b9edf5b7354f68a6bb", size = 82341 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/37/3fa718aaadd36e073891138dc3ebd919a71bafd4881c97d8a133265af191/argcomplete-3.5.2-py3-none-any.whl", hash = "sha256:036d020d79048a5d525bc63880d7a4b8d1668566b8a76daf1144c0bbe0f63472", size = 43506 }, +] + +[[package]] +name = "arrow" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, + { name = "types-python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2e/00/0f6e8fcdb23ea632c866620cc872729ff43ed91d284c866b515c6342b173/arrow-1.3.0.tar.gz", hash = "sha256:d4540617648cb5f895730f1ad8c82a65f2dad0166f57b75f3ca54759c4d67a85", size = 131960 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl", hash = "sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80", size = 66419 }, +] + +[[package]] +name = "attrs" +version = "24.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/0f/aafca9af9315aee06a89ffde799a10a582fe8de76c563ee80bbcdc08b3fb/attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346", size = 792678 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/21/5b6702a7f963e95456c0de2d495f67bf5fd62840ac655dc451586d23d39a/attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2", size = 63001 }, +] + +[[package]] +name = "binaryornot" +version = "0.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "chardet" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a7/fe/7ebfec74d49f97fc55cd38240c7a7d08134002b1e14be8c3897c0dd5e49b/binaryornot-0.4.4.tar.gz", hash = "sha256:359501dfc9d40632edc9fac890e19542db1a287bbcfa58175b66658392018061", size = 371054 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/7e/f7b6f453e6481d1e233540262ccbfcf89adcd43606f44a028d7f5fae5eb2/binaryornot-0.4.4-py2.py3-none-any.whl", hash = "sha256:b8b71173c917bddcd2c16070412e369c3ed7f0528926f70cac18a6c97fd563e4", size = 9006 }, +] + +[[package]] +name = "certifi" +version = "2024.12.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/bd/1d41ee578ce09523c81a15426705dd20969f5abf006d1afe8aeff0dd776a/certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db", size = 166010 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/32/8f6669fc4798494966bf446c8c4a162e0b5d893dff088afddf76414f70e1/certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56", size = 164927 }, +] + +[[package]] +name = "cfgv" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249 }, +] + +[[package]] +name = "chardet" +version = "5.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/f7b6ab21ec75897ed80c17d79b15951a719226b9fababf1e40ea74d69079/chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7", size = 2069618 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/6f/f5fbc992a329ee4e0f288c1fe0e2ad9485ed064cac731ed2fe47dcc38cbf/chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970", size = 199385 }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/4f/e1808dc01273379acc506d18f1504eb2d299bd4131743b9fc54d7be4df1e/charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e", size = 106620 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/0b/4b7a70987abf9b8196845806198975b6aab4ce016632f817ad758a5aa056/charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6", size = 194445 }, + { url = "https://files.pythonhosted.org/packages/50/89/354cc56cf4dd2449715bc9a0f54f3aef3dc700d2d62d1fa5bbea53b13426/charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf", size = 125275 }, + { url = "https://files.pythonhosted.org/packages/fa/44/b730e2a2580110ced837ac083d8ad222343c96bb6b66e9e4e706e4d0b6df/charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db", size = 119020 }, + { url = "https://files.pythonhosted.org/packages/9d/e4/9263b8240ed9472a2ae7ddc3e516e71ef46617fe40eaa51221ccd4ad9a27/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1", size = 139128 }, + { url = "https://files.pythonhosted.org/packages/6b/e3/9f73e779315a54334240353eaea75854a9a690f3f580e4bd85d977cb2204/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03", size = 149277 }, + { url = "https://files.pythonhosted.org/packages/1a/cf/f1f50c2f295312edb8a548d3fa56a5c923b146cd3f24114d5adb7e7be558/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284", size = 142174 }, + { url = "https://files.pythonhosted.org/packages/16/92/92a76dc2ff3a12e69ba94e7e05168d37d0345fa08c87e1fe24d0c2a42223/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15", size = 143838 }, + { url = "https://files.pythonhosted.org/packages/a4/01/2117ff2b1dfc61695daf2babe4a874bca328489afa85952440b59819e9d7/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8", size = 146149 }, + { url = "https://files.pythonhosted.org/packages/f6/9b/93a332b8d25b347f6839ca0a61b7f0287b0930216994e8bf67a75d050255/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2", size = 140043 }, + { url = "https://files.pythonhosted.org/packages/ab/f6/7ac4a01adcdecbc7a7587767c776d53d369b8b971382b91211489535acf0/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719", size = 148229 }, + { url = "https://files.pythonhosted.org/packages/9d/be/5708ad18161dee7dc6a0f7e6cf3a88ea6279c3e8484844c0590e50e803ef/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631", size = 151556 }, + { url = "https://files.pythonhosted.org/packages/5a/bb/3d8bc22bacb9eb89785e83e6723f9888265f3a0de3b9ce724d66bd49884e/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b", size = 149772 }, + { url = "https://files.pythonhosted.org/packages/f7/fa/d3fc622de05a86f30beea5fc4e9ac46aead4731e73fd9055496732bcc0a4/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565", size = 144800 }, + { url = "https://files.pythonhosted.org/packages/9a/65/bdb9bc496d7d190d725e96816e20e2ae3a6fa42a5cac99c3c3d6ff884118/charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7", size = 94836 }, + { url = "https://files.pythonhosted.org/packages/3e/67/7b72b69d25b89c0b3cea583ee372c43aa24df15f0e0f8d3982c57804984b/charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9", size = 102187 }, + { url = "https://files.pythonhosted.org/packages/f3/89/68a4c86f1a0002810a27f12e9a7b22feb198c59b2f05231349fbce5c06f4/charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114", size = 194617 }, + { url = "https://files.pythonhosted.org/packages/4f/cd/8947fe425e2ab0aa57aceb7807af13a0e4162cd21eee42ef5b053447edf5/charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed", size = 125310 }, + { url = "https://files.pythonhosted.org/packages/5b/f0/b5263e8668a4ee9becc2b451ed909e9c27058337fda5b8c49588183c267a/charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250", size = 119126 }, + { url = "https://files.pythonhosted.org/packages/ff/6e/e445afe4f7fda27a533f3234b627b3e515a1b9429bc981c9a5e2aa5d97b6/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920", size = 139342 }, + { url = "https://files.pythonhosted.org/packages/a1/b2/4af9993b532d93270538ad4926c8e37dc29f2111c36f9c629840c57cd9b3/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64", size = 149383 }, + { url = "https://files.pythonhosted.org/packages/fb/6f/4e78c3b97686b871db9be6f31d64e9264e889f8c9d7ab33c771f847f79b7/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23", size = 142214 }, + { url = "https://files.pythonhosted.org/packages/2b/c9/1c8fe3ce05d30c87eff498592c89015b19fade13df42850aafae09e94f35/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc", size = 144104 }, + { url = "https://files.pythonhosted.org/packages/ee/68/efad5dcb306bf37db7db338338e7bb8ebd8cf38ee5bbd5ceaaaa46f257e6/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d", size = 146255 }, + { url = "https://files.pythonhosted.org/packages/0c/75/1ed813c3ffd200b1f3e71121c95da3f79e6d2a96120163443b3ad1057505/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88", size = 140251 }, + { url = "https://files.pythonhosted.org/packages/7d/0d/6f32255c1979653b448d3c709583557a4d24ff97ac4f3a5be156b2e6a210/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90", size = 148474 }, + { url = "https://files.pythonhosted.org/packages/ac/a0/c1b5298de4670d997101fef95b97ac440e8c8d8b4efa5a4d1ef44af82f0d/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b", size = 151849 }, + { url = "https://files.pythonhosted.org/packages/04/4f/b3961ba0c664989ba63e30595a3ed0875d6790ff26671e2aae2fdc28a399/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d", size = 149781 }, + { url = "https://files.pythonhosted.org/packages/d8/90/6af4cd042066a4adad58ae25648a12c09c879efa4849c705719ba1b23d8c/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482", size = 144970 }, + { url = "https://files.pythonhosted.org/packages/cc/67/e5e7e0cbfefc4ca79025238b43cdf8a2037854195b37d6417f3d0895c4c2/charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67", size = 94973 }, + { url = "https://files.pythonhosted.org/packages/65/97/fc9bbc54ee13d33dc54a7fcf17b26368b18505500fc01e228c27b5222d80/charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b", size = 102308 }, + { url = "https://files.pythonhosted.org/packages/bf/9b/08c0432272d77b04803958a4598a51e2a4b51c06640af8b8f0f908c18bf2/charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079", size = 49446 }, +] + +[[package]] +name = "click" +version = "8.1.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "platform_system == 'Windows'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941 }, +] + +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "commitizen" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "argcomplete" }, + { name = "charset-normalizer" }, + { name = "colorama" }, + { name = "decli" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "questionary" }, + { name = "termcolor" }, + { name = "tomlkit" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/c5/66f1b977b48501a33f5fd33253aba14786483b08aba987718d272e99e732/commitizen-4.1.0.tar.gz", hash = "sha256:4f2d9400ec411aec1c738d4c63fc7fd5807cd6ddf6be970869e03e68b88ff718", size = 51252 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/f7/7f70adfbf3553ffdbe391eaacde72b21dbc1b4226ae56ca32e8ded1bf70b/commitizen-4.1.0-py3-none-any.whl", hash = "sha256:2e6c5fbd442cab4bcc5a04bc86ef2196ef84bcf611317d6c596e87f5bb4c09f5", size = 72282 }, +] + +[[package]] +name = "cookiecutter" +version = "2.6.0" +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/52/17/9f2cd228eb949a91915acd38d3eecdc9d8893dde353b603f0db7e9f6be55/cookiecutter-2.6.0.tar.gz", hash = "sha256:db21f8169ea4f4fdc2408d48ca44859349de2647fbe494a9d6c3edfc0542c21c", size = 158767 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/d9/0137658a353168ffa9d0fc14b812d3834772040858ddd1cb6eeaf09f7a44/cookiecutter-2.6.0-py3-none-any.whl", hash = "sha256:a54a8e37995e4ed963b3e82831072d1ad4b005af736bb17b99c2cbd9d41b6e2d", size = 39177 }, +] + +[[package]] +name = "cookiecutter-mlops-package" +version = "1.0.0" +source = { virtual = "." } + +[package.dev-dependencies] +dev = [ + { name = "commitizen" }, + { name = "invoke" }, + { name = "pre-commit" }, + { name = "pytest" }, + { name = "pytest-cookies" }, + { name = "pytest-shell-utilities" }, +] + +[package.metadata] + +[package.metadata.requires-dev] +dev = [ + { name = "commitizen", specifier = ">=4.1.0" }, + { name = "invoke", specifier = ">=2.2.0" }, + { name = "pre-commit", specifier = ">=4.0.1" }, + { name = "pytest", specifier = ">=8.3.4" }, + { name = "pytest-cookies", specifier = ">=0.7.0" }, + { name = "pytest-shell-utilities", specifier = ">=1.9.7" }, +] + +[[package]] +name = "decli" +version = "0.6.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3d/a0/a4658f93ecb589f479037b164dc13c68d108b50bf6594e54c820749f97ac/decli-0.6.2.tar.gz", hash = "sha256:36f71eb55fd0093895efb4f416ec32b7f6e00147dda448e3365cf73ceab42d6f", size = 7424 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/70/3ea48dc9e958d7d66c44c9944809181f1ca79aaef25703c023b5092d34ff/decli-0.6.2-py3-none-any.whl", hash = "sha256:2fc84106ce9a8f523ed501ca543bdb7e416c064917c12a59ebdc7f311a97b7ed", size = 7854 }, +] + +[[package]] +name = "distlib" +version = "0.3.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/dd/1bec4c5ddb504ca60fc29472f3d27e8d4da1257a854e1d96742f15c1d02d/distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403", size = 613923 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973 }, +] + +[[package]] +name = "distro" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277 }, +] + +[[package]] +name = "filelock" +version = "3.16.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/db/3ef5bb276dae18d6ec2124224403d1d67bccdbefc17af4cc8f553e341ab1/filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435", size = 18037 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/f8/feced7779d755758a52d1f6635d990b8d98dc0a29fa568bbe0625f18fdf3/filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0", size = 16163 }, +] + +[[package]] +name = "identify" +version = "2.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1a/5f/05f0d167be94585d502b4adf8c7af31f1dc0b1c7e14f9938a88fdbbcf4a7/identify-2.6.3.tar.gz", hash = "sha256:62f5dae9b5fef52c84cc188514e9ea4f3f636b1d8799ab5ebc475471f9e47a02", size = 99179 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/f5/09644a3ad803fae9eca8efa17e1f2aef380c7f0b02f7ec4e8d446e51d64a/identify-2.6.3-py2.py3-none-any.whl", hash = "sha256:9edba65473324c2ea9684b1f944fe3191db3345e50b6d04571d10ed164f8d7bd", size = 99049 }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, +] + +[[package]] +name = "invoke" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/42/127e6d792884ab860defc3f4d80a8f9812e48ace584ffc5a346de58cdc6c/invoke-2.2.0.tar.gz", hash = "sha256:ee6cbb101af1a859c7fe84f2a264c059020b0cb7fe3535f9424300ab568f6bd5", size = 299835 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/66/7f8c48009c72d73bc6bbe6eb87ac838d6a526146f7dab14af671121eb379/invoke-2.2.0-py3-none-any.whl", hash = "sha256:6ea924cc53d4f78e3d98bc436b08069a03077e6f85ad1ddaa8a116d7dad15820", size = 160274 }, +] + +[[package]] +name = "jinja2" +version = "3.1.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ed/55/39036716d19cab0747a5020fc7e907f362fbf48c984b14e62127f7e68e5d/jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369", size = 240245 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/80/3a54838c3fb461f6fec263ebf3a3a41771bd05190238de3486aae8540c36/jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d", size = 133271 }, +] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } +wheels = [ + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097 }, + { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, +] + +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, +] + +[[package]] +name = "nodeenv" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 }, +] + +[[package]] +name = "packaging" +version = "24.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, +] + +[[package]] +name = "platformdirs" +version = "4.3.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/fc/128cc9cb8f03208bdbf93d3aa862e16d376844a14f9a0ce5cf4507372de4/platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", size = 21302 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439 }, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, +] + +[[package]] +name = "pre-commit" +version = "4.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2e/c8/e22c292035f1bac8b9f5237a2622305bc0304e776080b246f3df57c4ff9f/pre_commit-4.0.1.tar.gz", hash = "sha256:80905ac375958c0444c65e9cebebd948b3cdb518f335a091a670a89d652139d2", size = 191678 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/8f/496e10d51edd6671ebe0432e33ff800aa86775d2d147ce7d43389324a525/pre_commit-4.0.1-py2.py3-none-any.whl", hash = "sha256:efde913840816312445dc98787724647c65473daefe420785f885e8ed9a06878", size = 218713 }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.36" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fb/93/180be2342f89f16543ec4eb3f25083b5b84eba5378f68efff05409fb39a9/prompt_toolkit-3.0.36.tar.gz", hash = "sha256:3e163f254bef5a03b146397d7c1963bd3e2812f0964bb9a24e6ec761fd28db63", size = 423863 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/37/791f1a6edd13c61cac85282368aa68cb0f3f164440fdf60032f2cc6ca34e/prompt_toolkit-3.0.36-py3-none-any.whl", hash = "sha256:aa64ad242a462c5ff0363a7b9cfe696c20d55d9fc60c11fd8e632d064804d305", size = 386414 }, +] + +[[package]] +name = "psutil" +version = "6.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/10/2a30b13c61e7cf937f4adf90710776b7918ed0a9c434e2c38224732af310/psutil-6.1.0.tar.gz", hash = "sha256:353815f59a7f64cdaca1c0307ee13558a0512f6db064e92fe833784f08539c7a", size = 508565 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/01/9e/8be43078a171381953cfee33c07c0d628594b5dbfc5157847b85022c2c1b/psutil-6.1.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6e2dcd475ce8b80522e51d923d10c7871e45f20918e027ab682f94f1c6351688", size = 247762 }, + { url = "https://files.pythonhosted.org/packages/1d/cb/313e80644ea407f04f6602a9e23096540d9dc1878755f3952ea8d3d104be/psutil-6.1.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:0895b8414afafc526712c498bd9de2b063deaac4021a3b3c34566283464aff8e", size = 248777 }, + { url = "https://files.pythonhosted.org/packages/65/8e/bcbe2025c587b5d703369b6a75b65d41d1367553da6e3f788aff91eaf5bd/psutil-6.1.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9dcbfce5d89f1d1f2546a2090f4fcf87c7f669d1d90aacb7d7582addece9fb38", size = 284259 }, + { url = "https://files.pythonhosted.org/packages/58/4d/8245e6f76a93c98aab285a43ea71ff1b171bcd90c9d238bf81f7021fb233/psutil-6.1.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:498c6979f9c6637ebc3a73b3f87f9eb1ec24e1ce53a7c5173b8508981614a90b", size = 287255 }, + { url = "https://files.pythonhosted.org/packages/27/c2/d034856ac47e3b3cdfa9720d0e113902e615f4190d5d1bdb8df4b2015fb2/psutil-6.1.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d905186d647b16755a800e7263d43df08b790d709d575105d419f8b6ef65423a", size = 288804 }, + { url = "https://files.pythonhosted.org/packages/ea/55/5389ed243c878725feffc0d6a3bc5ef6764312b6fc7c081faaa2cfa7ef37/psutil-6.1.0-cp37-abi3-win32.whl", hash = "sha256:1ad45a1f5d0b608253b11508f80940985d1d0c8f6111b5cb637533a0e6ddc13e", size = 250386 }, + { url = "https://files.pythonhosted.org/packages/11/91/87fa6f060e649b1e1a7b19a4f5869709fbf750b7c8c262ee776ec32f3028/psutil-6.1.0-cp37-abi3-win_amd64.whl", hash = "sha256:a8fb3752b491d246034fa4d279ff076501588ce8cbcdbb62c32fd7a377d996be", size = 254228 }, +] + +[[package]] +name = "pygments" +version = "2.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/62/8336eff65bcbc8e4cb5d05b55faf041285951b6e80f33e2bff2024788f31/pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199", size = 4891905 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a", size = 1205513 }, +] + +[[package]] +name = "pytest" +version = "8.3.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/05/35/30e0d83068951d90a01852cb1cef56e5d8a09d20c7f511634cc2f7e0372a/pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761", size = 1445919 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/92/76a1c94d3afee238333bc0a42b82935dd8f9cf8ce9e336ff87ee14d9e1cf/pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6", size = 343083 }, +] + +[[package]] +name = "pytest-cookies" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cookiecutter" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/2e/11a3e1abb4bbf10e0af3f194ba4c55600de3fe52417ef3594c18d28ecdbe/pytest-cookies-0.7.0.tar.gz", hash = "sha256:1aaa6b4def8238d0d1709d3d773b423351bfb671c1e3438664d824e0859d6308", size = 8840 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/f7/438af2f3a6c58f81d22c126707ee5d079f653a76961f4fb7d995e526a9c4/pytest_cookies-0.7.0-py3-none-any.whl", hash = "sha256:52770f090d77b16428f6a24a208e6be76addb2e33458035714087b4de49389ea", size = 6386 }, +] + +[[package]] +name = "pytest-helpers-namespace" +version = "2021.12.29" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e8/61/7f1a476b375c1238d152c164f7ffb694c662da8247816947be84b5fe2e8a/pytest-helpers-namespace-2021.12.29.tar.gz", hash = "sha256:792038247e0021beb966a7ea6e3a70ff5fcfba77eb72c6ec8fd6287af871c35b", size = 53355 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/a7/34844a563c425962658f093b359636deefd19eab6783896e0d378dae88a1/pytest_helpers_namespace-2021.12.29-py3-none-any.whl", hash = "sha256:d5c0262642998437a73d85cb6ae0db57d574facc551c4a4695e92ec50469eb98", size = 10501 }, +] + +[[package]] +name = "pytest-shell-utilities" +version = "1.9.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "psutil" }, + { name = "pytest" }, + { name = "pytest-helpers-namespace" }, + { name = "pytest-skip-markers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f9/de/6b4060bef2b650f87e01c3b5f2d0ee41f0b49f4bd903253941959fac08d2/pytest_shell_utilities-1.9.7.tar.gz", hash = "sha256:84a2283333925b43780509ccfd3d5be0d7db243b7414b89f1b63954bbcc9d3ec", size = 81231 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/3d/09b2335b10f6a292a8c99907634655acbe0054850934574c904cab172a86/pytest_shell_utilities-1.9.7-py3-none-any.whl", hash = "sha256:e9975718160d9d35c715e0b349ddb78afb43c810ac5c98a6d0714462c0456eb6", size = 28812 }, +] + +[[package]] +name = "pytest-skip-markers" +version = "1.5.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "distro" }, + { name = "pytest" }, + { name = "pywin32", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/27/8b/15c961e983eb3f71d277679cfe93c1c0cd698507c461f90208d80252534f/pytest_skip_markers-1.5.2.tar.gz", hash = "sha256:76d61be67f1fd639cca37830a4756b09cea5521361616e66344f7ef00b4d06c0", size = 75342 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/c3/de4b07aa387e5e529d4c095e39cc665eb67588c321e489f632ace5015c5c/pytest_skip_markers-1.5.2-py3-none-any.whl", hash = "sha256:416be63bebacb3c17020d6a65b4306cb685161a2071ca5e7a7df529627f7e0f7", size = 20167 }, +] + +[[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 } +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 }, +] + +[[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 } +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 }, +] + +[[package]] +name = "pywin32" +version = "308" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/7c/d00d6bdd96de4344e06c4afbf218bc86b54436a94c01c71a8701f613aa56/pywin32-308-cp312-cp312-win32.whl", hash = "sha256:587f3e19696f4bf96fde9d8a57cec74a57021ad5f204c9e627e15c33ff568897", size = 5939729 }, + { url = "https://files.pythonhosted.org/packages/21/27/0c8811fbc3ca188f93b5354e7c286eb91f80a53afa4e11007ef661afa746/pywin32-308-cp312-cp312-win_amd64.whl", hash = "sha256:00b3e11ef09ede56c6a43c71f2d31857cf7c54b0ab6e78ac659497abd2834f47", size = 6543015 }, + { url = "https://files.pythonhosted.org/packages/9d/0f/d40f8373608caed2255781a3ad9a51d03a594a1248cd632d6a298daca693/pywin32-308-cp312-cp312-win_arm64.whl", hash = "sha256:9b4de86c8d909aed15b7011182c8cab38c8850de36e6afb1f0db22b8959e3091", size = 7976033 }, + { url = "https://files.pythonhosted.org/packages/a9/a4/aa562d8935e3df5e49c161b427a3a2efad2ed4e9cf81c3de636f1fdddfd0/pywin32-308-cp313-cp313-win32.whl", hash = "sha256:1c44539a37a5b7b21d02ab34e6a4d314e0788f1690d65b48e9b0b89f31abbbed", size = 5938579 }, + { url = "https://files.pythonhosted.org/packages/c7/50/b0efb8bb66210da67a53ab95fd7a98826a97ee21f1d22949863e6d588b22/pywin32-308-cp313-cp313-win_amd64.whl", hash = "sha256:fd380990e792eaf6827fcb7e187b2b4b1cede0585e3d0c9e84201ec27b9905e4", size = 6542056 }, + { url = "https://files.pythonhosted.org/packages/26/df/2b63e3e4f2df0224f8aaf6d131f54fe4e8c96400eb9df563e2aae2e1a1f9/pywin32-308-cp313-cp313-win_arm64.whl", hash = "sha256:ef313c46d4c18dfb82a2431e3051ac8f112ccee1a34f29c263c583c568db63cd", size = 7974986 }, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, +] + +[[package]] +name = "questionary" +version = "2.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "prompt-toolkit" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/84/d0/d73525aeba800df7030ac187d09c59dc40df1c878b4fab8669bdc805535d/questionary-2.0.1.tar.gz", hash = "sha256:bcce898bf3dbb446ff62830c86c5c6fb9a22a54146f0f5597d3da43b10d8fc8b", size = 24726 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/e7/2dd8f59d1d328773505f78b85405ddb1cfe74126425d076ce72e65540b8b/questionary-2.0.1-py3-none-any.whl", hash = "sha256:8ab9a01d0b91b68444dff7f6652c1e754105533f083cbe27597c8110ecc230a2", size = 34248 }, +] + +[[package]] +name = "requests" +version = "2.32.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, +] + +[[package]] +name = "rich" +version = "13.9.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ab/3a/0316b28d0761c6734d6bc14e770d85506c986c85ffb239e688eeaab2c2bc/rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", size = 223149 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424 }, +] + +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, +] + +[[package]] +name = "termcolor" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/37/72/88311445fd44c455c7d553e61f95412cf89054308a1aa2434ab835075fc5/termcolor-2.5.0.tar.gz", hash = "sha256:998d8d27da6d48442e8e1f016119076b690d962507531df4890fcd2db2ef8a6f", size = 13057 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/be/df630c387a0a054815d60be6a97eb4e8f17385d5d6fe660e1c02750062b4/termcolor-2.5.0-py3-none-any.whl", hash = "sha256:37b17b5fc1e604945c2642c872a3764b5d547a48009871aea3edd3afa180afb8", size = 7755 }, +] + +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/a5/c0b6468d3824fe3fde30dbb5e1f687b291608f9473681bbf7dabbf5a87d7/text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8", size = 78154 }, +] + +[[package]] +name = "tomlkit" +version = "0.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/09/a439bec5888f00a54b8b9f05fa94d7f901d6735ef4e55dcec9bc37b5d8fa/tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79", size = 192885 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/b6/a447b5e4ec71e13871be01ba81f5dfc9d0af7e473da256ff46bc0e24026f/tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde", size = 37955 }, +] + +[[package]] +name = "types-python-dateutil" +version = "2.9.0.20241206" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/60/47d92293d9bc521cd2301e423a358abfac0ad409b3a1606d8fbae1321961/types_python_dateutil-2.9.0.20241206.tar.gz", hash = "sha256:18f493414c26ffba692a72369fea7a154c502646301ebfe3d56a04b3767284cb", size = 13802 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/b3/ca41df24db5eb99b00d97f89d7674a90cb6b3134c52fb8121b6d8d30f15c/types_python_dateutil-2.9.0.20241206-py3-none-any.whl", hash = "sha256:e248a4bc70a486d3e3ec84d0dc30eec3a5f979d6e7ee4123ae043eedbb987f53", size = 14384 }, +] + +[[package]] +name = "urllib3" +version = "2.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ed/63/22ba4ebfe7430b76388e7cd448d5478814d3032121827c12a2cc287e2260/urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9", size = 300677 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/d9/5f4c13cecde62396b0d3fe530a50ccea91e7dfc1ccf0e09c228841bb5ba8/urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", size = 126338 }, +] + +[[package]] +name = "virtualenv" +version = "20.28.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bf/75/53316a5a8050069228a2f6d11f32046cfa94fbb6cc3f08703f59b873de2e/virtualenv-20.28.0.tar.gz", hash = "sha256:2c9c3262bb8e7b87ea801d715fae4495e6032450c71d2309be9550e7364049aa", size = 7650368 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/f9/0919cf6f1432a8c4baa62511f8f8da8225432d22e83e3476f5be1a1edc6e/virtualenv-20.28.0-py3-none-any.whl", hash = "sha256:23eae1b4516ecd610481eda647f3a7c09aea295055337331bb4e6892ecce47b0", size = 4276702 }, +] + +[[package]] +name = "wcwidth" +version = "0.2.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166 }, +] diff --git a/{{cookiecutter.repository}}/.github/actions/setup/action.yml b/{{cookiecutter.repository}}/.github/actions/setup/action.yml index 09c8777..3426d72 100644 --- a/{{cookiecutter.repository}}/.github/actions/setup/action.yml +++ b/{{cookiecutter.repository}}/.github/actions/setup/action.yml @@ -3,9 +3,11 @@ description: Setup for project workflows runs: using: composite steps: - - run: pipx install invoke poetry - shell: bash - - uses: actions/setup-python@v5 + - name: Install uv + uses: astral-sh/setup-uv@v4 with: - python-version: {{cookiecutter.python_version}} - cache: poetry + enable-cache: true + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version-file: .python-version diff --git a/{{cookiecutter.repository}}/.github/workflows/check.yml b/{{cookiecutter.repository}}/.github/workflows/check.yml index c9a1bb3..ec648b9 100644 --- a/{{cookiecutter.repository}}/.github/workflows/check.yml +++ b/{{cookiecutter.repository}}/.github/workflows/check.yml @@ -2,7 +2,7 @@ name: Check on: pull_request: branches: - - main + - '*' concurrency: cancel-in-progress: true group: {% raw %}${{ github.workflow }}-${{ github.ref }}{% endraw %} @@ -12,5 +12,9 @@ jobs: steps: - uses: actions/checkout@v4 - uses: ./.github/actions/setup - - run: poetry install --with checks - - run: poetry run invoke checks + - run: uv sync --group=checks + - run: uv run invoke checks.format + - run: uv run invoke checks.type + - run: uv run invoke checks.code + - run: uv run invoke checks.security + - run: uv run invoke checks.coverage diff --git a/{{cookiecutter.repository}}/.github/workflows/publish.yml b/{{cookiecutter.repository}}/.github/workflows/publish.yml index 210dca7..e1e3db0 100644 --- a/{{cookiecutter.repository}}/.github/workflows/publish.yml +++ b/{{cookiecutter.repository}}/.github/workflows/publish.yml @@ -15,8 +15,8 @@ jobs: steps: - uses: actions/checkout@v4 - uses: ./.github/actions/setup - - run: poetry install --with docs - - run: poetry run invoke docs + - run: uv sync --group=docs + - run: uv run invoke docs - uses: JamesIves/github-pages-deploy-action@v4 with: folder: docs/ @@ -28,8 +28,8 @@ jobs: steps: - uses: actions/checkout@v4 - uses: ./.github/actions/setup - - run: poetry install --with dev - - run: poetry run invoke packages + - run: uv sync --only-dev + - run: uv run invoke packages - uses: docker/login-action@v3 with: registry: ghcr.io diff --git a/{{cookiecutter.repository}}/.pre-commit-config.yaml b/{{cookiecutter.repository}}/.pre-commit-config.yaml index 0b893e6..5746885 100644 --- a/{{cookiecutter.repository}}/.pre-commit-config.yaml +++ b/{{cookiecutter.repository}}/.pre-commit-config.yaml @@ -5,7 +5,7 @@ default_language_version: python: python{{cookiecutter.python_version}} repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 + rev: v5.0.0 hooks: - id: check-added-large-files - id: check-case-conflict @@ -16,18 +16,14 @@ repos: - id: end-of-file-fixer - id: mixed-line-ending - id: trailing-whitespace - - repo: https://github.com/python-poetry/poetry - rev: 1.8.3 - hooks: - - id: poetry-check - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.5.0 + rev: v0.8.1 hooks: - id: ruff - id: ruff-format # - repo: https://github.com/commitizen-tools/commitizen - # rev: v3.27.0 + # rev: v4.0.0 # hooks: # - id: commitizen # - id: commitizen-branch - # stages: [push] + # stages: [pre-push] diff --git a/{{cookiecutter.repository}}/Dockerfile b/{{cookiecutter.repository}}/Dockerfile index b13260e..cd59030 100644 --- a/{{cookiecutter.repository}}/Dockerfile +++ b/{{cookiecutter.repository}}/Dockerfile @@ -1,6 +1,6 @@ # https://docs.docker.com/engine/reference/builder/ -FROM python:{{cookiecutter.python_version}} +FROM ghcr.io/astral-sh/uv:python{{cookiecutter.python_version}}-bookworm COPY dist/*.whl . -RUN pip install *.whl +RUN uv pip install --system *.whl CMD ["{{cookiecutter.repository}}", "--help"] diff --git a/{{cookiecutter.repository}}/LICENCE.txt b/{{cookiecutter.repository}}/LICENSE.txt similarity index 100% rename from {{cookiecutter.repository}}/LICENCE.txt rename to {{cookiecutter.repository}}/LICENSE.txt diff --git a/{{cookiecutter.repository}}/MLproject b/{{cookiecutter.repository}}/MLproject index 483fdaf..19b41db 100644 --- a/{{cookiecutter.repository}}/MLproject +++ b/{{cookiecutter.repository}}/MLproject @@ -5,5 +5,5 @@ python_env: python_env.yaml entry_points: main: parameters: - job: string - command: "PYTHONPATH=src python -m {{cookiecutter.package}} {job}" + conf_file: path + command: "PYTHONPATH=src python -m {{cookiecutter.package}} {conf_file}" diff --git a/{{cookiecutter.repository}}/README.md b/{{cookiecutter.repository}}/README.md index 4dfb13e..e07f42d 100644 --- a/{{cookiecutter.repository}}/README.md +++ b/{{cookiecutter.repository}}/README.md @@ -10,14 +10,14 @@ # Installation -Use the package manager [Poetry](https://python-poetry.org/): +Use the package manager [uv](https://docs.astral.sh/uv/): ```bash -poetry install +uv sync ``` # Usage ```bash -poetry run {{cookiecutter.repository}} +uv run {{cookiecutter.repository}} ``` diff --git a/{{cookiecutter.repository}}/confs/.gitkeep b/{{cookiecutter.repository}}/confs/inference.yaml similarity index 100% rename from {{cookiecutter.repository}}/confs/.gitkeep rename to {{cookiecutter.repository}}/confs/inference.yaml diff --git a/{{cookiecutter.repository}}/confs/training.yaml b/{{cookiecutter.repository}}/confs/training.yaml new file mode 100644 index 0000000..e69de29 diff --git a/{{cookiecutter.repository}}/confs/tuning.yaml b/{{cookiecutter.repository}}/confs/tuning.yaml new file mode 100644 index 0000000..e69de29 diff --git a/{{cookiecutter.repository}}/poetry.toml b/{{cookiecutter.repository}}/poetry.toml deleted file mode 100644 index 7783e1b..0000000 --- a/{{cookiecutter.repository}}/poetry.toml +++ /dev/null @@ -1,5 +0,0 @@ -# https://python-poetry.org/docs/configuration/ - -[virtualenvs] -in-project = true -prefer-active-python = true diff --git a/{{cookiecutter.repository}}/pyproject.toml b/{{cookiecutter.repository}}/pyproject.toml index c6f7a0c..9416f63 100644 --- a/{{cookiecutter.repository}}/pyproject.toml +++ b/{{cookiecutter.repository}}/pyproject.toml @@ -1,54 +1,57 @@ -# https://python-poetry.org/docs/pyproject/ +# https://docs.astral.sh/uv/reference/settings/ +# https://packaging.python.org/en/latest/guides/writing-pyproject-toml/ # PROJECT -[tool.poetry] +[project] name = "{{cookiecutter.repository}}" version = "{{cookiecutter.version}}" description = "{{cookiecutter.description}}" -repository = "https://github.com/{{cookiecutter.user}}/{{cookiecutter.repository}}" -documentation = "https://{{cookiecutter.user}}.github.io/{{cookiecutter.repository}}/" -authors = ["{{cookiecutter.user}}"] +authors = [{ name = "{{cookiecutter.user}}" }] readme = "README.md" -license = "{{cookiecutter.license}}" -packages = [{ include = "{{cookiecutter.package}}", from = "src" }] +requires-python = ">={{cookiecutter.python_version}}" +dependencies = [ + "mlflow>={{cookiecutter.mlflow_version}}", +] +license = { file = "LICENSE.txt" } +keywords = ["mlops", "python", "package"] + +# LINKS + +[project.urls] +Homepage = "https://github.com/{{cookiecutter.user}}/{{cookiecutter.repository}}" +Documentation = "https://{{cookiecutter.user}}.github.io/{{cookiecutter.repository}}/" +Repository = "https://github.com/{{cookiecutter.user}}/{{cookiecutter.repository}}e" +"Bug Tracker" = "https://github.com/{{cookiecutter.user}}/{{cookiecutter.repository}}/issues" +Changelog = "https://github.com/{{cookiecutter.user}}/{{cookiecutter.repository}}/blob/main/CHANGELOG.md" # SCRIPTS -[tool.poetry.scripts] +[project.scripts] {{cookiecutter.repository}} = "{{cookiecutter.package}}.scripts:main" # DEPENDENCIES -[tool.poetry.dependencies] -python = "^{{cookiecutter.python_version}}" -mlflow = "^{{cookiecutter.mlflow_version}}" -setuptools = "^71.1.0" - -[tool.poetry.group.checks.dependencies] -bandit = "^1.7.9" -coverage = "^7.5.4" -mypy = "^1.10.1" -pytest = "^8.2.2" -pytest-cov = "^5.0.0" -pytest-xdist = "^3.6.1" -ruff = "^0.5.0" - -[tool.poetry.group.commits.dependencies] -commitizen = "^3.27.0" -pre-commit = "^3.7.1" - -[tool.poetry.group.dev.dependencies] -invoke = "^2.2.0" - -[tool.poetry.group.docs.dependencies] -pdoc = "^14.5.1" - -[tool.poetry.group.notebooks.dependencies] -ipykernel = "^6.29.4" -nbformat = "^5.10.4" - -# CONFIGURATIONS +[dependency-groups] +checks = [ + "bandit>=1.8.0", + "coverage>=7.6.8", + "mypy>=1.13.0", + "pytest-cov>=6.0.0", + "pytest-mock>=3.14.0", + "pytest-xdist>=3.6.1", + "pytest>=8.3.3", + "ruff>=0.8.1", +] +commits = ["commitizen>=4.0.0", "pre-commit>=4.0.1"] +dev = ["invoke>=2.2.0"] +docs = ["pdoc>=15.0.0"] +notebooks = ["ipykernel>=6.29.5", "nbformat>=5.10.4"] + +# TOOLS + +[tool.uv] +default-groups = ["checks", "commits", "dev", "docs", "notebooks"] [tool.bandit] targets = ["src"] @@ -57,7 +60,7 @@ targets = ["src"] name = "cz_conventional_commits" tag_format = "v$version" version_scheme = "pep440" -version_provider = "poetry" +version_provider = "pep621" update_changelog_on_bump = true [tool.coverage.run] @@ -67,7 +70,6 @@ omit = ["__main__.py"] [tool.mypy] pretty = true -strict = true python_version = "{{cookiecutter.python_version}}" check_untyped_defs = true ignore_missing_imports = true @@ -94,5 +96,5 @@ convention = "google" # SYSTEMS [build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" +requires = ["hatchling"] +build-backend = "hatchling.build" diff --git a/{{cookiecutter.repository}}/tasks/__init__.py b/{{cookiecutter.repository}}/tasks/__init__.py index 6a6751c..a7e7b04 100644 --- a/{{cookiecutter.repository}}/tasks/__init__.py +++ b/{{cookiecutter.repository}}/tasks/__init__.py @@ -1,4 +1,4 @@ -"""Task collections for the project.""" +"""Task collections of the project.""" # mypy: ignore-errors diff --git a/{{cookiecutter.repository}}/tasks/checks.py b/{{cookiecutter.repository}}/tasks/checks.py index 853d109..1046835 100644 --- a/{{cookiecutter.repository}}/tasks/checks.py +++ b/{{cookiecutter.repository}}/tasks/checks.py @@ -1,4 +1,4 @@ -"""Check tasks for pyinvoke.""" +"""Check tasks of the project.""" # %% IMPORTS @@ -8,48 +8,42 @@ # %% TASKS -@task -def poetry(ctx: Context) -> None: - """Check poetry config files.""" - ctx.run("poetry check --lock") - - @task def format(ctx: Context) -> None: """Check the formats with ruff.""" - ctx.run("poetry run ruff format --check src/ tasks/ tests/") + ctx.run("uv run ruff format --check src/ tasks/ tests/") @task def type(ctx: Context) -> None: """Check the types with mypy.""" - ctx.run("poetry run mypy src/ tasks/ tests/") + ctx.run("uv run mypy src/ tasks/ tests/") @task def code(ctx: Context) -> None: """Check the codes with ruff.""" - ctx.run("poetry run ruff check src/ tasks/ tests/") + ctx.run("uv run ruff check src/ tasks/ tests/") @task def test(ctx: Context) -> None: """Check the tests with pytest.""" - ctx.run("poetry run pytest --numprocesses='auto' tests/") + ctx.run("uv run pytest --numprocesses=auto tests/") @task def security(ctx: Context) -> None: """Check the security with bandit.""" - ctx.run("poetry run bandit --recursive --configfile=pyproject.toml src/") + ctx.run("uv run bandit --recursive --configfile=pyproject.toml src/") @task def coverage(ctx: Context) -> None: """Check the coverage with coverage.""" - ctx.run("poetry run pytest --numprocesses='auto' --cov=src/ --cov-fail-under=80 tests/") + ctx.run("uv run pytest --numprocesses=auto --cov=src/ --cov-fail-under=80 tests/") -@task(pre=[poetry, format, type, code, security, coverage], default=True) +@task(pre=[format, type, code, security, coverage], default=True) def all(_: Context) -> None: """Run all check tasks.""" diff --git a/{{cookiecutter.repository}}/tasks/cleans.py b/{{cookiecutter.repository}}/tasks/cleans.py index e29b7d0..9b13acd 100644 --- a/{{cookiecutter.repository}}/tasks/cleans.py +++ b/{{cookiecutter.repository}}/tasks/cleans.py @@ -1,4 +1,4 @@ -"""Clean tasks for pyinvoke.""" +"""Cleaning tasks of the project.""" # %% IMPORTS @@ -77,9 +77,9 @@ def venv(ctx: Context) -> None: @task -def poetry(ctx: Context) -> None: - """Clean poetry lock file.""" - ctx.run("rm -f poetry.lock") +def uv(ctx: Context) -> None: + """Clean uv lock file.""" + ctx.run("rm -f uv.lock") @task @@ -117,7 +117,7 @@ def folders(_: Context) -> None: """Run all folders tasks.""" -@task(pre=[venv, poetry, python]) +@task(pre=[venv, uv, python]) def sources(_: Context) -> None: """Run all sources tasks.""" diff --git a/{{cookiecutter.repository}}/tasks/commits.py b/{{cookiecutter.repository}}/tasks/commits.py index 88b087b..2f48bad 100644 --- a/{{cookiecutter.repository}}/tasks/commits.py +++ b/{{cookiecutter.repository}}/tasks/commits.py @@ -1,4 +1,4 @@ -"""Commits tasks for pyinvoke.""" +"""Commit tasks of the project.""" # %% IMPORTS @@ -11,19 +11,19 @@ @task def info(ctx: Context) -> None: """Print a guide for messages.""" - ctx.run("poetry run cz info") + ctx.run("uv run cz info") @task def bump(ctx: Context) -> None: """Bump the version of the package.""" - ctx.run("poetry run cz bump", pty=True) + ctx.run("uv run cz bump", pty=True) @task def commit(ctx: Context) -> None: """Commit all changes with a message.""" - ctx.run("poetry run cz commit", pty=True) + ctx.run("uv run cz commit", pty=True) @task(pre=[commit], default=True) diff --git a/{{cookiecutter.repository}}/tasks/containers.py b/{{cookiecutter.repository}}/tasks/containers.py index 87510f9..b6401fb 100644 --- a/{{cookiecutter.repository}}/tasks/containers.py +++ b/{{cookiecutter.repository}}/tasks/containers.py @@ -1,4 +1,4 @@ -"""Container tasks for pyinvoke.""" +"""Container tasks of the project.""" # %% IMPORTS diff --git a/{{cookiecutter.repository}}/tasks/docs.py b/{{cookiecutter.repository}}/tasks/docs.py index 21eea9e..3f0b34c 100644 --- a/{{cookiecutter.repository}}/tasks/docs.py +++ b/{{cookiecutter.repository}}/tasks/docs.py @@ -1,4 +1,4 @@ -"""Docs tasks for pyinvoke.""" +"""Documentation tasks of the project.""" # %% IMPORTS @@ -18,14 +18,14 @@ @task def serve(ctx: Context, format: str = DOC_FORMAT, port: int = 8088) -> None: """Serve the API docs with pdoc.""" - ctx.run(f"poetry run pdoc --docformat={format} --port={port} src/{ctx.project.package}") + ctx.run(f"uv run pdoc --docformat={format} --port={port} src/{ctx.project.package}") @task def api(ctx: Context, format: str = DOC_FORMAT, output_dir: str = OUTPUT_DIR) -> None: """Generate the API docs with pdoc.""" ctx.run( - f"poetry run pdoc --docformat={format} --output-directory={output_dir} src/{ctx.project.package}" + f"uv run pdoc --docformat={format} --output-directory={output_dir} src/{ctx.project.package}" ) diff --git a/{{cookiecutter.repository}}/tasks/formats.py b/{{cookiecutter.repository}}/tasks/formats.py index 4631bae..cd83b17 100644 --- a/{{cookiecutter.repository}}/tasks/formats.py +++ b/{{cookiecutter.repository}}/tasks/formats.py @@ -1,4 +1,4 @@ -"""Format tasks for pyinvoke.""" +"""Format tasks of the project.""" # %% IMPORTS @@ -11,13 +11,13 @@ @task def imports(ctx: Context) -> None: """Format python imports with ruff.""" - ctx.run("poetry run ruff check --select I --fix") + ctx.run("uv run ruff check --select I --fix") @task def sources(ctx: Context) -> None: """Format python sources with ruff.""" - ctx.run("poetry run ruff format src/ tasks/ tests/") + ctx.run("uv run ruff format src/ tasks/ tests/") @task(pre=[imports, sources], default=True) diff --git a/{{cookiecutter.repository}}/tasks/installs.py b/{{cookiecutter.repository}}/tasks/installs.py index 05669be..f6754a2 100644 --- a/{{cookiecutter.repository}}/tasks/installs.py +++ b/{{cookiecutter.repository}}/tasks/installs.py @@ -1,4 +1,4 @@ -"""Install tasks for pyinvoke.""" +"""Install tasks of the project.""" # %% IMPORTS @@ -9,18 +9,18 @@ @task -def poetry(ctx: Context) -> None: - """Install poetry packages.""" - ctx.run("poetry install") +def uv(ctx: Context) -> None: + """Install uv packages.""" + ctx.run("uv sync --all-groups") @task def pre_commit(ctx: Context) -> None: """Install pre-commit hooks on git.""" - ctx.run("poetry run pre-commit install --hook-type pre-push") - ctx.run("poetry run pre-commit install --hook-type commit-msg") + ctx.run("uv run pre-commit install --hook-type=pre-push") + ctx.run("uv run pre-commit install --hook-type=commit-msg") -@task(pre=[poetry, pre_commit], default=True) +@task(pre=[uv, pre_commit], default=True) def all(_: Context) -> None: """Run all install tasks.""" diff --git a/{{cookiecutter.repository}}/tasks/mlflow.py b/{{cookiecutter.repository}}/tasks/mlflow.py index 0053931..3ccf363 100644 --- a/{{cookiecutter.repository}}/tasks/mlflow.py +++ b/{{cookiecutter.repository}}/tasks/mlflow.py @@ -1,4 +1,4 @@ -"""Mlflow tasks for pyinvoke.""" +"""Mlflow tasks of the project.""" # %% IMPORTS @@ -11,16 +11,19 @@ @task def doctor(ctx: Context) -> None: """Run mlflow doctor.""" - ctx.run("poetry run mlflow doctor") + ctx.run("uv run mlflow doctor") @task def serve( - ctx: Context, host: str = "127.0.0.1", port: str = "5000", backend_uri: str = "./mlruns" + ctx: Context, + host: str = "127.0.0.1", + port: str = "5000", + backend_store_uri: str = "./mlruns", ) -> None: - """Start the mlflow server.""" + """Start an mlflow server.""" ctx.run( - f"poetry run mlflow server --host={host} --port={port} --backend-store-uri={backend_uri}" + f"uv run mlflow server --host={host} --port={port} --backend-store-uri={backend_store_uri}" ) diff --git a/{{cookiecutter.repository}}/tasks/packages.py b/{{cookiecutter.repository}}/tasks/packages.py index 38e9c3d..e476a60 100644 --- a/{{cookiecutter.repository}}/tasks/packages.py +++ b/{{cookiecutter.repository}}/tasks/packages.py @@ -1,4 +1,4 @@ -"""Package tasks for pyinvoke.""" +"""Package tasks of the project.""" # %% IMPORTS @@ -7,17 +7,13 @@ from . import cleans -# %% CONFIGS - -BUILD_FORMAT = "wheel" - # %% TASKS @task(pre=[cleans.dist]) -def build(ctx: Context, format: str = BUILD_FORMAT) -> None: +def build(ctx: Context) -> None: """Build the python package.""" - ctx.run(f"poetry build --format={format}") + ctx.run("uv build --wheel") @task(pre=[build], default=True) diff --git a/{{cookiecutter.repository}}/tasks/projects.py b/{{cookiecutter.repository}}/tasks/projects.py index cbe9c36..a7d18e3 100644 --- a/{{cookiecutter.repository}}/tasks/projects.py +++ b/{{cookiecutter.repository}}/tasks/projects.py @@ -1,10 +1,11 @@ -"""Project tasks for pyinvoke.""" +"""Project tasks of the project.""" # mypy: disable-error-code="arg-type" # %% IMPORTS import json + from invoke.context import Context from invoke.tasks import call, task @@ -20,7 +21,11 @@ @task def requirements(ctx: Context) -> None: """Export the project requirements file.""" - ctx.run(f"poetry export --without-urls --without-hashes --output={REQUIREMENTS}") + ctx.run( + "uv export --format=requirements-txt --no-dev " + "--no-hashes --no-editable --no-emit-project " + f"--output-file={REQUIREMENTS}" + ) @task(pre=[requirements]) @@ -31,26 +36,35 @@ def environment(ctx: Context) -> None: configuration: dict[str, object] = {"python": python} with open(REQUIREMENTS, "r") as reader: dependencies: list[str] = [] - for line in reader: - dependency = line.split(" ")[0] - if "pywin32" not in dependency: - dependencies.append(dependency) + for line in reader.readlines(): + dependency = line.split(" ")[0].strip() + if "pywin32" in dependency or "#" in dependency: + continue + dependencies.append(dependency) configuration["dependencies"] = dependencies with open(ENVIRONMENT, "w") as writer: # Safe as YAML is a superset of JSON json.dump(configuration, writer, indent=4) - writer.write("\n") # add new line at the end + writer.write("\n") # add new line at the end @task def run(ctx: Context, job: str) -> None: """Run an mlflow project from the MLproject file.""" ctx.run( - f"poetry run mlflow run --experiment-name={ctx.project.repository}" - f" --run-name={job.capitalize()} -P job={job} ." + f"uv run mlflow run --experiment-name={ctx.project.repository}" + f" --run-name={job.capitalize()} -P conf_file=confs/{job}.yaml ." ) -@task(pre=[environment, call(run, job="main")], default=True) +@task( + pre=[ + environment, + call(run, job="tuning"), + call(run, job="training"), + call(run, job="inference"), + ], + default=True, +) def all(_: Context) -> None: """Run all project tasks.""" diff --git a/{{cookiecutter.repository}}/tests/test_scripts.py b/{{cookiecutter.repository}}/tests/test_scripts.py index 568b8c1..a77d674 100644 --- a/{{cookiecutter.repository}}/tests/test_scripts.py +++ b/{{cookiecutter.repository}}/tests/test_scripts.py @@ -4,6 +4,7 @@ # %% FUNCTIONS + def test_main() -> None: # given argv = ["x", "y", "z"] From 1e390773333b2d6716c0221c274eddc03853e71b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9d=C3=A9ric=20Hurier=20=28Fmind=29?= Date: Sun, 15 Dec 2024 12:59:47 +0100 Subject: [PATCH 2/5] =?UTF-8?q?bump:=20version=201.0.0=20=E2=86=92=202.0.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 7 +++++++ pyproject.toml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29..0c28b8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1,7 @@ +## v2.0.0 (2024-12-15) + +### Feat + +- **upgrade**: upgrade to v3 of the mlops-python-package, replacing poetry with uv + +## v1.0.0 (2024-07-28) diff --git a/pyproject.toml b/pyproject.toml index b471fd4..861e8c2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ [project] name = "cookiecutter-mlops-package" -version = "1.0.0" +version = "2.0.0" description = "Build and deploy Python packages and Docker images for MLOps projects." authors = [{ name = "Médéric HURIER", email = "github@fmind.dev" }] readme = "README.md" From 728f9dec3861fbae050b4c9c91ab41f6f3dc90c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9d=C3=A9ric=20Hurier=20=28Fmind=29?= Date: Sun, 15 Dec 2024 13:22:58 +0100 Subject: [PATCH 3/5] refactor(cicd): fix ci/cd bug --- uv.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uv.lock b/uv.lock index ded24ca..836fe3f 100644 --- a/uv.lock +++ b/uv.lock @@ -173,7 +173,7 @@ wheels = [ [[package]] name = "cookiecutter-mlops-package" -version = "1.0.0" +version = "2.0.0" source = { virtual = "." } [package.dev-dependencies] From 0bd56c0d365c8879e950614fc62c2f826732aea4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9d=C3=A9ric=20Hurier=20=28Fmind=29?= Date: Sun, 15 Dec 2024 13:32:38 +0100 Subject: [PATCH 4/5] fix(project): switch from mlflow to uv BREAKING CHANGE: --- README.md | 4 +--- {{cookiecutter.repository}}/MLproject | 9 --------- {{cookiecutter.repository}}/tasks/projects.py | 7 ++----- 3 files changed, 3 insertions(+), 17 deletions(-) delete mode 100644 {{cookiecutter.repository}}/MLproject diff --git a/README.md b/README.md index 92c8434..c7785a4 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,6 @@ You have the freedom to structure your `src/` and `tests/` directories according * **Uv Integration:** Effortless dependency management and packaging with [uv](https://docs.astral.sh/uv/). * **Automated Testing and Checks:** Pre-configured workflows using [Pytest](https://docs.pytest.org/), [Ruff](https://docs.astral.sh/ruff/), [Mypy](https://mypy.readthedocs.io/), [Bandit](https://bandit.readthedocs.io/), and [Coverage](https://coverage.readthedocs.io/) to ensure code quality, style, security, and type safety. * **Pre-commit Hooks:** Automatic code formatting and linting with [Ruff](https://docs.astral.sh/ruff/) and other pre-commit hooks to maintain consistency. -* **MLflow Project Ready:** An MLproject file for executing jobs using [MLflow](https://mlflow.org/), allowing for easy experimentation and tracking. * **Dockerized Deployment:** Dockerfile and docker-compose.yml for building and running the package within a containerized environment ([Docker](https://www.docker.com/)). * **Invoke Task Automation:** [PyInvoke](https://www.pyinvoke.org/) tasks to simplify development workflows such as cleaning, installing, formatting, checking, building, documenting, and running MLflow projects. * **Comprehensive Documentation:** [pdoc](https://pdoc.dev/) generates API documentation, and Markdown files provide clear usage instructions. @@ -74,7 +73,6 @@ git init - `tasks/`: PyInvoke tasks for automation. - `Dockerfile`: Configuration for building your Docker image. - `docker-compose.yml`: Orchestration file for running MLflow and your project. -- `MLproject`: MLflow project definition. 5. **Start developing!** @@ -98,7 +96,7 @@ After installing dependencies and setting up MLflow: invoke projects ``` -This will execute the default jobs defined in your [`MLproject`](https://github.com/fmind/cookiecutter-mlops-package/blob/main/%7B%7Bcookiecutter.repository%7D%7D/MLproject) file. You can specify different jobs using the `-P job=your_job_name` flag. +This will execute the job with the configuration file in your `confs` folder. ### Building and Running Your Docker Image diff --git a/{{cookiecutter.repository}}/MLproject b/{{cookiecutter.repository}}/MLproject deleted file mode 100644 index 19b41db..0000000 --- a/{{cookiecutter.repository}}/MLproject +++ /dev/null @@ -1,9 +0,0 @@ -# https://mlflow.org/docs/latest/projects.html - -name: {{cookiecutter.name}} -python_env: python_env.yaml -entry_points: - main: - parameters: - conf_file: path - command: "PYTHONPATH=src python -m {{cookiecutter.package}} {conf_file}" diff --git a/{{cookiecutter.repository}}/tasks/projects.py b/{{cookiecutter.repository}}/tasks/projects.py index a7d18e3..c175183 100644 --- a/{{cookiecutter.repository}}/tasks/projects.py +++ b/{{cookiecutter.repository}}/tasks/projects.py @@ -50,11 +50,8 @@ def environment(ctx: Context) -> None: @task def run(ctx: Context, job: str) -> None: - """Run an mlflow project from the MLproject file.""" - ctx.run( - f"uv run mlflow run --experiment-name={ctx.project.repository}" - f" --run-name={job.capitalize()} -P conf_file=confs/{job}.yaml ." - ) + """Run the project for the given job file.""" + ctx.run(f"uv run {ctx.project.repository} confs/{job}.yaml") @task( From ff0718e14bd99ee50082a49a3dd1c38a9b9dd8f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9d=C3=A9ric=20Hurier=20=28Fmind=29?= Date: Sun, 15 Dec 2024 13:32:41 +0100 Subject: [PATCH 5/5] =?UTF-8?q?bump:=20version=202.0.0=20=E2=86=92=203.0.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 10 ++++++++++ pyproject.toml | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c28b8f..f779320 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## v3.0.0 (2024-12-15) + +### Fix + +- **project**: switch from mlflow to uv + +### Refactor + +- **cicd**: fix ci/cd bug + ## v2.0.0 (2024-12-15) ### Feat diff --git a/pyproject.toml b/pyproject.toml index 861e8c2..9e75822 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ [project] name = "cookiecutter-mlops-package" -version = "2.0.0" +version = "3.0.0" description = "Build and deploy Python packages and Docker images for MLOps projects." authors = [{ name = "Médéric HURIER", email = "github@fmind.dev" }] readme = "README.md"