diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..ad1c6f2 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,27 @@ +name: Tests +on: + pull_request: + branches: + - '*' +permissions: + contents: read + id-token: write +concurrency: + cancel-in-progress: true + group: ${{ github.workflow }}-${{ github.ref }} +jobs: + cookiecutter: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install uv + uses: astral-sh/setup-uv@v5 + with: + enable-cache: true + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version-file: .python-version + - name: Test + run: uv run just test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ed29e00 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +# https://git-scm.com/docs/gitignore + +# IDE +/.idea/ +/.vscode/ + +# Python +.venv/ +*.py[cod] +__pycache__/ +.mypy_cache/ +.ruff_cache/ +.pytest_cache/ +.ipynb_checkpoints/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..efc69e0 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,15 @@ +# https://pre-commit.com +# https://pre-commit.com/hooks.html + +default_language_version: + python: python3.13 +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: 'v5.0.0' + hooks: + - id: check-added-large-files + - id: check-case-conflict + - id: check-merge-conflict + - id: end-of-file-fixer + - id: mixed-line-ending + - id: trailing-whitespace diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..24ee5b1 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.13 diff --git a/README.md b/README.md index 838df9f..b22bd2a 100644 --- a/README.md +++ b/README.md @@ -23,11 +23,11 @@ You have the freedom to structure your `src/` and `tests/` directories according (This section was copied into the created project's README so tool info is available to users.) * **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. * **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 the project. +* **uv+just Task Automation:** [just](https://github.com/casey/just) commands to simplify development workflows such as cleaning, installing, formatting, checking, building, documenting and running the project. * **Comprehensive Documentation:** [pdoc](https://pdoc.dev/) generates API documentation, and Markdown files provide clear usage instructions. * **GitHub Workflow Integration:** Continuous integration and deployment workflows are set up using [GitHub Actions](https://github.com/features/actions), automating testing, checks, and publishing. @@ -67,20 +67,23 @@ git init - `src/{{cookiecutter.package}}`: Your Python package source code. - `tests/`: Unit tests for your package. -- `tasks/`: PyInvoke tasks for automation. +- `tasks/`: `just` commands for automation. - `Dockerfile`: Configuration for building your Docker image. - `docker-compose.yml`: Orchestration file for running your project. 4. **Start developing!** -Use the provided Invoke tasks to manage your development workflow: - -- `invoke installs`: Install dependencies and pre-commit hooks. -- `invoke formats`: Format your code. -- `invoke checks`: Run code quality, type, security, and test checks. -- `invoke docs`: Generate API documentation. -- `invoke packages`: Build your Python package. -- `invoke containers`: Build and run your Docker image. +Use the provided `just` commands to manage your development workflow: + +- `uv run just check`: Run code quality, type, security, and test checks. +- `uv run just clean`: Clean up generated files. +- `uv run just commit`: Commit changes to your repository. +- `uv run just doc`: Generate API documentation. +- `uv run just docker`: Build and run your Docker image. +- `uv run just format`: Format your code with Ruff. +- `uv run just install`: Install dependencies, pre-commit hooks, and GitHub rulesets. +- `uv run just package`: Build your Python package. +- `uv run just project`: Run the project in the CLI. ## Example Usage diff --git a/justfile b/justfile new file mode 100644 index 0000000..8b93c66 --- /dev/null +++ b/justfile @@ -0,0 +1,33 @@ +# https://just.systems/man/en/ + +# REQUIRES + +find := require("find") +rm := require("rm") +uv := require("uv") + +# DEFAULTS + +# display help information +default: + @just --list + +# TASKS + +# clean the project +clean: + rm -rf .venv/ + rm -rf .mypy_cache/ + rm -rf .pytest_cache/ + +# install the project +install: + uv sync --all-groups + +# setup the project hooks +hooks: + uv run pre-commit install + +# run the project unit tests +test: + uv run pytest tests/ 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..1083e03 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,45 +1,41 @@ -# 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 "] +version = "4.1.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" -license = "MIT" -package-mode = false +requires-python = ">=3.13" +license = { file = "LICENSE.txt" } # DEPENDENCIES -[tool.poetry.dependencies] -python = "^3.12" +[dependency-groups] +dev = [ + "commitizen>=4.1.0", + "pre-commit>=4.0.1", + "pytest-cookies>=0.7.0", + "pytest-shell-utilities>=1.9.7", + "pytest>=8.3.4", + "rust-just>=1.39.0", +] -[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.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 deleted file mode 100644 index 5460133..0000000 --- a/tasks.py +++ /dev/null @@ -1,25 +0,0 @@ -"""Tasks of the project.""" - -# %% IMPORTS - -from invoke import task -from invoke.context import Context - -# %% TASKS - -@task -def install(ctx: Context) -> None: - """Install the project.""" - ctx.run("poetry install") - - -@task -def hooks(ctx: Context) -> None: - """Setup the project hooks.""" - ctx.run("poetry run pre-commit install") - - -@task -def test(ctx: Context) -> None: - """Run the project unit tests.""" - ctx.run("poetry run pytest tests/") diff --git a/tests/test_cookiecutter.py b/tests/test_cookiecutter.py index 50df86d..4d7111c 100644 --- a/tests/test_cookiecutter.py +++ b/tests/test_cookiecutter.py @@ -9,14 +9,14 @@ COMMANDS = [ "git init", - "invoke cleans.reset", - "invoke installs", - "invoke formats", - "invoke checks", - "invoke docs", - "invoke projects", - "invoke packages", - "invoke containers", + "uv run just clean", + "uv run just install", + "uv run just format", + "uv run just check", + "uv run just doc", + "uv run just project", + "uv run just package", + "uv run just docker", ] # %% TESTS @@ -25,14 +25,13 @@ def test_project_generation(cookies: Cookies) -> None: """Test the generation of the project.""" # given - context = { - "user": "test", + context = { + "user": "tester", "name": "MLOps 123", - "license": "apache-2", + "license": "Apache-2.0", # Note: needs to be a "valid SPDX identifier" "version": "1.0.0", - "description": "DONE", - "python_version": "3.12", - "mlflow_version": "2.14.3", + "description": "A test project.", + "python_version": "3.13", } repository = context['name'].lower().replace(' ', '-') package = repository.replace('-', '_') @@ -47,13 +46,12 @@ def test_project_generation(cookies: Cookies) -> None: assert result.context == { "user": context['user'], "name": context['name'], - "repository": repository, "package": package, + "repository": repository, "license": context['license'], "version": context['version'], "description": context['description'], "python_version": context['python_version'], - "mlflow_version": context['mlflow_version'], } # - commands shell = Subprocess(cwd=result.project_path) diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..55e726b --- /dev/null +++ b/uv.lock @@ -0,0 +1,655 @@ +version = 1 +requires-python = ">=3.13" + +[[package]] +name = "argcomplete" +version = "3.5.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0c/be/6c23d80cb966fb8f83fb1ebfb988351ae6b0554d0c3a613ee4531c026597/argcomplete-3.5.3.tar.gz", hash = "sha256:c12bf50eded8aebb298c7b7da7a5ff3ee24dffd9f5281867dfe1424b58c55392", size = 72999 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/08/2a4db06ec3d203124c967fc89295e85a202e5cbbcdc08fd6a64b65217d1e/argcomplete-3.5.3-py3-none-any.whl", hash = "sha256:2ab2c4a215c59fd6caaff41a869480a23e8f6a5f910b266c1808037f4e375b61", size = 43569 }, +] + +[[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 = "25.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/49/7c/fdf464bcc51d23881d110abd74b512a42b3d5d376a55a831b44c603ae17f/attrs-25.1.0.tar.gz", hash = "sha256:1c97078a80c814273a76b2a298a932eb681c87415c11dee0a6921de7f1b02c3e", size = 810562 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/30/d4986a882011f9df997a55e6becd864812ccfcd821d64aac8570ee39f719/attrs-25.1.0-py3-none-any.whl", hash = "sha256:c75a69e28a550a7e93789579c22aa26b0f5b83b75dc4e08fe092980051e1090a", size = 63152 }, +] + +[[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 = "2025.1.31" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 }, +] + +[[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.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698 }, + { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162 }, + { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263 }, + { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966 }, + { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992 }, + { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162 }, + { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972 }, + { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095 }, + { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668 }, + { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073 }, + { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732 }, + { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391 }, + { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702 }, + { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 }, +] + +[[package]] +name = "click" +version = "8.1.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "platform_system == 'Windows'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 }, +] + +[[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.4.1" +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/b5/fd/cd449bed87a26ecb61c950410e2d94e97ac31bf1f3ec69cc718b215384ce/commitizen-4.4.1.tar.gz", hash = "sha256:626d9f545fb9b2db42305e16ef35d6348a35081a80527bad863a05a7ba0bec21", size = 52345 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/0a/03fd9b5b7d9de11ff5e7c5a73b66c17775d00c9eea07b585d4fd7bf45a31/commitizen-4.4.1-py3-none-any.whl", hash = "sha256:98dbee784cc74fd1b24915e265e99ce81caccd64e54cb42b347a37d1dd2a4cd8", size = 74882 }, +] + +[[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 = "4.1.0" +source = { virtual = "." } + +[package.dev-dependencies] +dev = [ + { name = "commitizen" }, + { name = "pre-commit" }, + { name = "pytest" }, + { name = "pytest-cookies" }, + { name = "pytest-shell-utilities" }, + { name = "rust-just" }, +] + +[package.metadata] + +[package.metadata.requires-dev] +dev = [ + { name = "commitizen", specifier = ">=4.1.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" }, + { name = "rust-just", specifier = ">=1.39.0" }, +] + +[[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.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/9c/0b15fb47b464e1b663b1acd1253a062aa5feecb07d4e597daea542ebd2b5/filelock-3.17.0.tar.gz", hash = "sha256:ee4e77401ef576ebb38cd7f13b9b28893194acc20a8e68e18730ba9c0e54660e", size = 18027 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/ec/00d68c4ddfedfe64159999e5f8a98fb8442729a63e2077eb9dcd89623d27/filelock-3.17.0-py3-none-any.whl", hash = "sha256:533dc2f7ba78dc2f0f531fc6c4940addf7b70a481e269a5a3b93be94ffbe8338", size = 16164 }, +] + +[[package]] +name = "identify" +version = "2.6.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/fa/5eb460539e6f5252a7c5a931b53426e49258cde17e3d50685031c300a8fd/identify-2.6.8.tar.gz", hash = "sha256:61491417ea2c0c5c670484fd8abbb34de34cdae1e5f39a73ee65e48e4bb663fc", size = 99249 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/8c/4bfcab2d8286473b8d83ea742716f4b79290172e75f91142bc1534b05b9a/identify-2.6.8-py2.py3-none-any.whl", hash = "sha256:83657f0f766a3c8d0eaea16d4ef42494b39b34629a4b3192a9d020d349b3e255", size = 99109 }, +] + +[[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 = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 }, +] + +[[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/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.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2a/13/b62d075317d8686071eb843f0bb1f195eb332f48869d3c31a4c6f1e063ac/pre_commit-4.1.0.tar.gz", hash = "sha256:ae3f018575a588e30dfddfab9a05448bfbd6b73d78709617b5a2b853549716d4", size = 193330 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/b3/df14c580d82b9627d173ceea305ba898dca135feb360b6d84019d0803d3b/pre_commit-4.1.0-py2.py3-none-any.whl", hash = "sha256:d29e7cb346295bcc1cc75fc3e92e343495e3ea0196c9ec6ba53f49f10ab6ae7b", size = 220560 }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.50" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/e1/bd15cb8ffdcfeeb2bdc215de3c3cffca11408d829e4b8416dcfe71ba8854/prompt_toolkit-3.0.50.tar.gz", hash = "sha256:544748f3860a2623ca5cd6d2795e7a14f3d0e1c3c9728359013f79877fc89bab", size = 429087 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/ea/d836f008d33151c7a1f62caf3d8dd782e4d15f6a43897f64480c2b8de2ad/prompt_toolkit-3.0.50-py3-none-any.whl", hash = "sha256:9b6427eb19e479d98acff65196a307c555eb567989e6d88ebbb1b509d9779198", size = 387816 }, +] + +[[package]] +name = "psutil" +version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2a/80/336820c1ad9286a4ded7e845b2eccfcb27851ab8ac6abece774a6ff4d3de/psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456", size = 497003 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/e6/2d26234410f8b8abdbf891c9da62bee396583f713fb9f3325a4760875d22/psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25", size = 238051 }, + { url = "https://files.pythonhosted.org/packages/04/8b/30f930733afe425e3cbfc0e1468a30a18942350c1a8816acfade80c005c4/psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da", size = 239535 }, + { url = "https://files.pythonhosted.org/packages/2a/ed/d362e84620dd22876b55389248e522338ed1bf134a5edd3b8231d7207f6d/psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91", size = 275004 }, + { url = "https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34", size = 277986 }, + { url = "https://files.pythonhosted.org/packages/eb/a2/709e0fe2f093556c17fbafda93ac032257242cabcc7ff3369e2cb76a97aa/psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993", size = 279544 }, + { url = "https://files.pythonhosted.org/packages/50/e6/eecf58810b9d12e6427369784efe814a1eec0f492084ce8eb8f4d89d6d61/psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99", size = 241053 }, + { url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885 }, +] + +[[package]] +name = "pygments" +version = "2.19.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 }, +] + +[[package]] +name = "pytest" +version = "8.3.5" +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/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 }, +] + +[[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/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/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.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "prompt-toolkit" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a8/b8/d16eb579277f3de9e56e5ad25280fab52fc5774117fb70362e8c2e016559/questionary-2.1.0.tar.gz", hash = "sha256:6302cdd645b19667d8f6e6634774e9538bfcd1aad9be287e743d96cacaf95587", size = 26775 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/3f/11dd4cd4f39e05128bfd20138faea57bec56f9ffba6185d276e3107ba5b2/questionary-2.1.0-py3-none-any.whl", hash = "sha256:44174d237b68bc828e4878c763a9ad6790ee61990e0ae72927694ead57bab8ec", size = 36747 }, +] + +[[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 = "rust-just" +version = "1.39.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/e5/37254d353a23f567ac218ddd2c461337fafe3a43d44dcd6f0c8e72d096f4/rust_just-1.39.0.tar.gz", hash = "sha256:247d0b293924cc8089a73428c9c03a3c2c0627bb8f205addb976ded0681f0dac", size = 1395439 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/9e/c7151bfa84c1cd3ac4c11a60d1a2f074b7a244ae96959d968e8b9e8e3f29/rust_just-1.39.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3845ab10254c994ddebcf489b30c53a24c1d11585c9e0eeaf1cb0da422bee87f", size = 1781993 }, + { url = "https://files.pythonhosted.org/packages/e4/0f/c93ef08567835356033bee162c2e5e06b018811f5188b94ef3e74dafe7eb/rust_just-1.39.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fd5c12118a8d65266ccdacfbc24bab26f77d509caaf263095cb96611ea6ce7e8", size = 1652880 }, + { url = "https://files.pythonhosted.org/packages/f3/e7/22647f9c18537046940b3b4159377665912acccbacedd775917610c0390d/rust_just-1.39.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:428d07b1e798777c4e9a8c245539d72743be095558010f0a86823e1c442930f9", size = 1771788 }, + { url = "https://files.pythonhosted.org/packages/d5/1f/2a36afad5eeca6756fc8c87e08d102cdadf8e4b31c5f8bcb6f12c109b5b4/rust_just-1.39.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:135a7a65a8641b00a2fe7f3156a97ab7052e4830a922a71e67ca4e38ccd54cd2", size = 1778377 }, + { url = "https://files.pythonhosted.org/packages/97/5b/45effb44bbfab892774a239446920cfb9b3d999921fe7cff3d99b36394a7/rust_just-1.39.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94eb45e585fda019f7f9cbac198e10e31f81c704371887cbdec9b7a1ae2e0d29", size = 1896235 }, + { url = "https://files.pythonhosted.org/packages/b5/12/7e88a7e917c2e933846a32a2f537786829b48c827a6029e85943d5eeadc0/rust_just-1.39.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de4a8566ca1eb87b5ff2669a6dd9474b16977c5d712534a5a9c7a950271da2d0", size = 1954347 }, + { url = "https://files.pythonhosted.org/packages/9a/36/5dc917a2c0a0b66f0cc4bb0be4985090deefa33433dd869649a4ce2bc8f3/rust_just-1.39.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7ecd8fd862729c243498951caa54d778ff480c2524039280ff3ebb9a64299f", size = 2450909 }, + { url = "https://files.pythonhosted.org/packages/da/fc/b9224b354da8a89b38cd4145ec0e9e089635a45c1459231e349647cdad64/rust_just-1.39.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:576229024d2ef8fc696d5a049ecd0d8f3d9b920a32e76f65e95840d24d804101", size = 1895058 }, + { url = "https://files.pythonhosted.org/packages/0b/ef/985cb93c9dd36f9bc41f26b3ce6d420c69fb18166ac61783bced2c328f75/rust_just-1.39.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c1cd9240e2c1b352d7ccc6b89ce84fcc0352f15bb9660cdc6bc34802b36251b6", size = 1767816 }, + { url = "https://files.pythonhosted.org/packages/50/43/fc644746a7479fadbe6878ae9fa1da83860104d1e64a49de47306fa1a591/rust_just-1.39.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d6eff0461df7e36eba6e7f0addf16ef98563cf8cb483e4c8393be5456d6af5c6", size = 1791992 }, + { url = "https://files.pythonhosted.org/packages/32/d9/6924547c02afba3a49b14f53bed09b54d4292016b830f366d7ed1f80288a/rust_just-1.39.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0dfcc49a5fa126ba923b58e48921fd117e429660495577a854494c6ced3134c9", size = 1884357 }, + { url = "https://files.pythonhosted.org/packages/15/96/7e8d3795588c2e8e629f9f0d9cdd0cf1ba1bef5bf9c8b64aae33c78f1993/rust_just-1.39.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:826203ad02c869ad8621993a608adb01394ef9c9c9ca6aa7dd7875b1f272aa46", size = 1936706 }, + { url = "https://files.pythonhosted.org/packages/23/28/2492c4b7c8f0e526f52a263f20b951880387f992151bbc46c6c8914786a8/rust_just-1.39.0-py3-none-win32.whl", hash = "sha256:dcef0926b287449e853b878f6f34759a797d017cefb83afbcd74820d37259b78", size = 1577519 }, + { url = "https://files.pythonhosted.org/packages/6f/c2/1d576be1ca714df4a90255cfbcb4ec06d9aafbf7b14924c2881a8596a68e/rust_just-1.39.0-py3-none-win_amd64.whl", hash = "sha256:3139f3f76434a8ebbf35b213d149e647c4d9546312b438e262df7ec41e7ef7bc", size = 1705761 }, +] + +[[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.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369 }, +] + +[[package]] +name = "virtualenv" +version = "20.29.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f1/88/dacc875dd54a8acadb4bcbfd4e3e86df8be75527116c91d8f9784f5e9cab/virtualenv-20.29.2.tar.gz", hash = "sha256:fdaabebf6d03b5ba83ae0a02cfe96f48a716f4fae556461d180825866f75b728", size = 4320272 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/fa/849483d56773ae29740ae70043ad88e068f98a6401aa819b5d6bee604683/virtualenv-20.29.2-py3-none-any.whl", hash = "sha256:febddfc3d1ea571bdb1dc0f98d7b45d24def7428214d4fb73cc486c9568cce6a", size = 4301478 }, +] + +[[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/CHANGELOG.md b/{{cookiecutter.repository}}/.env.example similarity index 100% rename from CHANGELOG.md rename to {{cookiecutter.repository}}/.env.example diff --git a/{{cookiecutter.repository}}/.github/actions/setup/action.yml b/{{cookiecutter.repository}}/.github/actions/setup/action.yml index 09c8777..cd4f2ef 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@v5 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/rulesets/main.json b/{{cookiecutter.repository}}/.github/rulesets/main.json new file mode 100644 index 0000000..98d3035 --- /dev/null +++ b/{{cookiecutter.repository}}/.github/rulesets/main.json @@ -0,0 +1,58 @@ +{ + "name": "main", + "target": "branch", + "enforcement": "active", + "conditions": { + "ref_name": { + "exclude": [], + "include": [ + "~DEFAULT_BRANCH" + ] + } + }, + "rules": [ + { + "type": "deletion" + }, + { + "type": "required_linear_history" + }, + { + "type": "pull_request", + "parameters": { + "required_approving_review_count": 0, + "dismiss_stale_reviews_on_push": true, + "require_code_owner_review": false, + "require_last_push_approval": false, + "required_review_thread_resolution": false, + "allowed_merge_methods": [ + "squash", + "rebase" + ] + } + }, + { + "type": "required_status_checks", + "parameters": { + "strict_required_status_checks_policy": true, + "do_not_enforce_on_create": false, + "required_status_checks": [ + { + "context": "checks", + "integration_id": 15368 + } + ] + } + }, + { + "type": "non_fast_forward" + } + ], + "bypass_actors": [ + { + "actor_id": 5, + "actor_type": "RepositoryRole", + "bypass_mode": "always" + } + ] +} diff --git a/{{cookiecutter.repository}}/.github/workflows/check.yml b/{{cookiecutter.repository}}/.github/workflows/check.yml index c9a1bb3..53508f1 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=check + - run: uv run just check-code + - run: uv run just check-type + - run: uv run just check-format + - run: uv run just check-security + - run: uv run just check-coverage diff --git a/{{cookiecutter.repository}}/.github/workflows/publish.yml b/{{cookiecutter.repository}}/.github/workflows/publish.yml index 210dca7..c3a4814 100644 --- a/{{cookiecutter.repository}}/.github/workflows/publish.yml +++ b/{{cookiecutter.repository}}/.github/workflows/publish.yml @@ -5,7 +5,7 @@ on: - edited - published env: - DOCKER_IMAGE: ghcr.io/{{cookiecutter.user}}/{{cookiecutter.repository}} + DOCKER_IMAGE: ghcr.io/fmind/mlops-python-package concurrency: cancel-in-progress: true group: publish-workflow @@ -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=doc + - run: uv run just doc - 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 just package - uses: docker/login-action@v3 with: registry: ghcr.io diff --git a/{{cookiecutter.repository}}/.gitignore b/{{cookiecutter.repository}}/.gitignore index 23bdea3..0abc593 100644 --- a/{{cookiecutter.repository}}/.gitignore +++ b/{{cookiecutter.repository}}/.gitignore @@ -29,168 +29,3 @@ # Python *.py[cod] __pycache__/ - -# https://github.com/github/gitignore/blob/main/Python.gitignore - -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ -cover/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -.pybuilder/ -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -# For a library or package, you might want to ignore these files since the code is -# intended to run in multiple environments; otherwise, check them in: -# .python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# poetry -# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control -#poetry.lock - -# pdm -# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. -#pdm.lock -# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it -# in version control. -# https://pdm.fming.dev/latest/usage/project/#working-with-version-control -.pdm.toml -.pdm-python -.pdm-build/ - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ - -# pytype static type analyzer -.pytype/ - -# Cython debug symbols -cython_debug/ - -# PyCharm -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ diff --git a/{{cookiecutter.repository}}/.pre-commit-config.yaml b/{{cookiecutter.repository}}/.pre-commit-config.yaml index 0b893e6..98b4fa3 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,18 @@ 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.9.9' hooks: - id: ruff - id: ruff-format + - repo: https://github.com/PyCQA/bandit + rev: '1.8.3' + hooks: + - id: bandit # - repo: https://github.com/commitizen-tools/commitizen - # rev: v3.27.0 + # rev: 'v4.4.1' # hooks: # - id: commitizen # - id: commitizen-branch - # stages: [push] + # stages: [pre-push] diff --git a/{{cookiecutter.repository}}/.python-version b/{{cookiecutter.repository}}/.python-version new file mode 100644 index 0000000..a2b2ca5 --- /dev/null +++ b/{{cookiecutter.repository}}/.python-version @@ -0,0 +1 @@ +{{cookiecutter.python_version}} diff --git a/{{cookiecutter.repository}}/Dockerfile b/{{cookiecutter.repository}}/Dockerfile index 19d9a46..8059e6f 100644 --- a/{{cookiecutter.repository}}/Dockerfile +++ b/{{cookiecutter.repository}}/Dockerfile @@ -1,15 +1,14 @@ # https://docs.docker.com/engine/reference/builder/ -FROM python:{{cookiecutter.python_version}}-slim -# Install dependencies with poetry -RUN pip install --progress-bar off poetry==1.8.2 -COPY poetry.lock pyproject.toml ./ -RUN poetry export -f requirements.txt --output requirements.txt --without-hashes -RUN pip install --progress-bar off -r requirements.txt -RUN rm poetry.lock pyproject.toml +FROM ghcr.io/astral-sh/uv:python{{cookiecutter.python_version}}-bookworm +COPY dist/*.whl . +RUN uv pip install --system *.whl +CMD ["{{cookiecutter.repository}}", "--help"] COPY ./src /app ENV PYTHONPATH="${PYTHONPATH}:/app" WORKDIR /app -CMD ["uvicorn", "example_app.main:app", "--host", "0.0.0.0"] +CMD ["{{cookiecutter.repository}}", "--help"] +# Example command +# CMD ["uvicorn", "example_app.main:app", "--host", "0.0.0.0"] diff --git a/{{cookiecutter.repository}}/README.md b/{{cookiecutter.repository}}/README.md index e827219..2d38585 100644 --- a/{{cookiecutter.repository}}/README.md +++ b/{{cookiecutter.repository}}/README.md @@ -6,7 +6,7 @@ [![License](https://img.shields.io/github/license/{{cookiecutter.user}}/{{cookiecutter.repository}})](https://github.com/{{cookiecutter.user}}/{{cookiecutter.repository}}/blob/main/LICENCE.txt) [![Release](https://img.shields.io/github/v/release/{{cookiecutter.user}}/{{cookiecutter.repository}})](https://github.com/{{cookiecutter.user}}/{{cookiecutter.repository}}/releases) -# Description +# Description {{cookiecutter.description}}. @@ -14,67 +14,52 @@ # Installation -Initialize your project with the provided invoke command. -```bash -# Install dependencies and pre-commit hooks -invoke installs +Initialize your project with the provided `just` command. +```bash +# Install dependencies and pre-commit hooks +uv run just install ``` - # Usage (The source comes with an example python package and an example FastAPI app. Delete this comment and add details for your application.) Test the example package ```bash -poetry run {{cookiecutter.repository}} +uv run {{cookiecutter.repository}} ``` Test the example API with Docker: -```bash -poetry add fastapi uvicorn +```bash +uv add fastapi uvicorn -# Invoke docker compose -invoke containers +# Invoke docker compose +uv run just docker-compose -# Or run with docker compose -docker compose up --build +# Or run with docker compose +docker compose up --build -# Or run with docker -# Note: specify platform if running on Apple M chip -docker build --platform linux/amd64 -t angle-to-geo-image -f Dockerfile . -docker run -it --platform linux/amd64 --name angle-to-geo-test-ctr -p 8000:8000 angle-to-geo-image +# Or run with docker +# Note: specify platform if running on Apple M chip +docker build --platform linux/amd64 -t {{cookiecutter.repository}}-image -f Dockerfile . +docker run -it --platform linux/amd64 --name {{cookiecutter.repository}}-ctr -p 8000:8000 {{cookiecutter.repository}}-image ``` +Test the API using the local environment: ```bash -poetry add fastapi uvicorn -# Test the API using the local environment -cd src -poetry run uvicorn example_app.main:app --reload +cd src +uv run uvicorn example_app.main:app --reload ``` ## Development Features +(This section was copied into the created project's README so tool info is available to users.) + * **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. * **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. +* **uv+just Task Automation:** [just](https://github.com/casey/just) commands to simplify development workflows such as cleaning, installing, formatting, checking, building, documenting and running the project. * **Comprehensive Documentation:** [pdoc](https://pdoc.dev/) generates API documentation, and Markdown files provide clear usage instructions. * **GitHub Workflow Integration:** Continuous integration and deployment workflows are set up using [GitHub Actions](https://github.com/features/actions), automating testing, checks, and publishing. -Use the provided Invoke tasks to manage your development workflow: - -- `invoke installs`: Install dependencies and pre-commit hooks. -- `invoke formats`: Format your code. -- `invoke checks`: Run code quality, type, security, and test checks. -- `invoke docs`: Generate API documentation. -- `invoke packages`: Build your Python package. -- `invoke containers`: Build and run your Docker image. - -### Using Ruff with PyCharm - -Note: First version of [Ruff plugin](https://github.com/koxudaxi/ruff-pycharm-plugin) would have issues (v0.0.27) apparently due to my PyCharm version (2023.2). - -I upgraded to (2024.2) and Ruff 0.0.39 and now autosave works. - diff --git a/{{cookiecutter.repository}}/docker-compose.yml b/{{cookiecutter.repository}}/docker-compose.yml index bb1c479..134016e 100644 --- a/{{cookiecutter.repository}}/docker-compose.yml +++ b/{{cookiecutter.repository}}/docker-compose.yml @@ -3,7 +3,7 @@ services: fastapi_app: # Name your service build: . # Build the image from the current directory (.) - command: ["uvicorn", "angle_to_geo.main:app", "--host", "0.0.0.0", "--reload"] + command: ["uvicorn", "{{cookiecutter.repository}}.main:app", "--host", "0.0.0.0", "--reload"] ports: - "8000:8000" # Map container port 8000 to host port 8000 volumes: diff --git a/{{cookiecutter.repository}}/invoke.yaml b/{{cookiecutter.repository}}/invoke.yaml deleted file mode 100644 index 272d689..0000000 --- a/{{cookiecutter.repository}}/invoke.yaml +++ /dev/null @@ -1,8 +0,0 @@ -# https://docs.pyinvoke.org/en/latest/index.html - -run: - echo: true -project: - name: {{cookiecutter.name}} - package: {{cookiecutter.package}} - repository: {{cookiecutter.repository}} diff --git a/{{cookiecutter.repository}}/justfile b/{{cookiecutter.repository}}/justfile new file mode 100644 index 0000000..e274338 --- /dev/null +++ b/{{cookiecutter.repository}}/justfile @@ -0,0 +1,37 @@ +# https://just.systems/man/en/ + +# REQUIRES + +docker := require("docker") +find := require("find") +rm := require("rm") +uv := require("uv") + +# SETTINGS + +set dotenv-load := true + +# VARIABLES + +PACKAGE := "{{cookiecutter.package}}" +REPOSITORY := "{{cookiecutter.repository}}" +SOURCES := "src" +TESTS := "tests" + +# DEFAULTS + +# display help information +default: + @just --list + +# IMPORTS + +import 'tasks/check.just' +import 'tasks/clean.just' +import 'tasks/commit.just' +import 'tasks/doc.just' +import 'tasks/docker.just' +import 'tasks/format.just' +import 'tasks/install.just' +import 'tasks/package.just' +import 'tasks/project.just' 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 abe7d16..97cbc34 100644 --- a/{{cookiecutter.repository}}/pyproject.toml +++ b/{{cookiecutter.repository}}/pyproject.toml @@ -1,49 +1,54 @@ -# 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 = [ + "hatchling>=1.27.0", +] + +# 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" # SCRIPTS -[tool.poetry.scripts] +[project.scripts] {{cookiecutter.repository}} = "{{cookiecutter.package}}.scripts:main" # DEPENDENCIES -[tool.poetry.dependencies] -python = "^{{cookiecutter.python_version}}" -loguru = "^0.7.2" - -[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" - -# CONFIGURATIONS +[dependency-groups] +check = [ + "bandit>=1.8.3", + "coverage>=7.6.12", + "mypy>=1.15.0", + "pytest>=8.3.5", + "pytest-cov>=6.0.0", + "pytest-mock>=3.14.0", + "pytest-xdist>=3.6.1", + "ruff>=0.9.9", +] +commit = ["commitizen>=4.4.1", "pre-commit>=4.1.0"] +dev = ["rust-just>=1.39.0"] +doc = ["pdoc>=15.0.1"] + +# TOOLS + +[tool.uv] +default-groups = ["check", "commit", "dev", "doc"] [tool.bandit] targets = ["src"] @@ -52,7 +57,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] @@ -62,11 +67,12 @@ omit = ["__main__.py"] [tool.mypy] pretty = true -strict = true -python_version = "3.12" +python_version = "3.13" check_untyped_defs = true ignore_missing_imports = true -disable_error_code = "import-untyped" +strict = false +disable_error_code = ["import-untyped", "disallow-untyped-decorators"] +# See https://mypy.readthedocs.io/en/stable/config_file.html#untyped-definitions-and-calls [tool.pytest.ini_options] addopts = "--verbosity=2" @@ -76,8 +82,7 @@ pythonpath = ["src"] fix = true indent-width = 4 line-length = 100 -target-version = "py312" -src = ["src"] +target-version = "py313" [tool.ruff.lint] extend-select = ["I"] @@ -94,5 +99,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}}/src/example_app/__init__.py b/{{cookiecutter.repository}}/src/example_app/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/{{cookiecutter.repository}}/src/example_app/main.py b/{{cookiecutter.repository}}/src/example_app/main.py index b5b63c5..41bbc5b 100644 --- a/{{cookiecutter.repository}}/src/example_app/main.py +++ b/{{cookiecutter.repository}}/src/example_app/main.py @@ -1,15 +1,8 @@ -""" -Setup: - pip install fastapi uvicorn - -Run: - uvicorn main:app --reload -""" - +# Ensure to install before running from fastapi import FastAPI app = FastAPI() @app.get("/") -def hello_world(): - return {"Hello": "World"} \ No newline at end of file +def hello_world() -> dict[str, str]: + return {"Hello": "World"} diff --git a/{{cookiecutter.repository}}/tasks/__init__.py b/{{cookiecutter.repository}}/tasks/__init__.py deleted file mode 100644 index 9d2aa99..0000000 --- a/{{cookiecutter.repository}}/tasks/__init__.py +++ /dev/null @@ -1,33 +0,0 @@ -"""Task collections for the project.""" - -# mypy: ignore-errors - -# %% IMPORTS - -from invoke import Collection - -from . import ( - checks, - cleans, - commits, - containers, - docs, - formats, - installs, - packages, -) - -# %% NAMESPACES - -ns = Collection() - -# %% COLLECTIONS - -ns.add_collection(checks) -ns.add_collection(cleans) -ns.add_collection(commits) -ns.add_collection(containers) -ns.add_collection(docs) -ns.add_collection(formats) -ns.add_collection(installs) -ns.add_collection(packages) diff --git a/{{cookiecutter.repository}}/tasks/check.just b/{{cookiecutter.repository}}/tasks/check.just new file mode 100644 index 0000000..f7b84ba --- /dev/null +++ b/{{cookiecutter.repository}}/tasks/check.just @@ -0,0 +1,33 @@ +# run check tasks +[group('check')] +check: check-code check-type check-format check-security check-coverage + +# check code quality +[group('check')] +check-code: + {% raw %}uv run ruff check {{SOURCES}} {{TESTS}}{% endraw %} + +# check code coverage +[group('check')] +check-coverage numprocesses="auto" cov_fail_under="80": + {% raw %}uv run pytest --numprocesses={{numprocesses}} --cov={{SOURCES}} --cov-fail-under={{cov_fail_under}} {{TESTS}}{% endraw %} + +# check code format +[group('check')] +check-format: + {% raw %}uv run ruff format --check {{SOURCES}} {{TESTS}}{% endraw %} + +# check code security +[group('check')] +check-security: + {% raw %}uv run bandit --recursive --configfile=pyproject.toml {{SOURCES}}{% endraw %} + +# check unit tests +[group('check')] +check-test numprocesses="auto": + {% raw %}uv run pytest --numprocesses={{numprocesses}} {{TESTS}}{% endraw %} + +# check code typing +[group('check')] +check-type: + {% raw %}uv run mypy {{SOURCES}} {{TESTS}}{% endraw %} diff --git a/{{cookiecutter.repository}}/tasks/checks.py b/{{cookiecutter.repository}}/tasks/checks.py deleted file mode 100644 index 853d109..0000000 --- a/{{cookiecutter.repository}}/tasks/checks.py +++ /dev/null @@ -1,55 +0,0 @@ -"""Check tasks for pyinvoke.""" - -# %% IMPORTS - -from invoke.context import Context -from invoke.tasks import task - -# %% 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/") - - -@task -def type(ctx: Context) -> None: - """Check the types with mypy.""" - ctx.run("poetry 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/") - - -@task -def test(ctx: Context) -> None: - """Check the tests with pytest.""" - ctx.run("poetry 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/") - - -@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/") - - -@task(pre=[poetry, format, type, code, security, coverage], default=True) -def all(_: Context) -> None: - """Run all check tasks.""" diff --git a/{{cookiecutter.repository}}/tasks/clean.just b/{{cookiecutter.repository}}/tasks/clean.just new file mode 100644 index 0000000..21061a5 --- /dev/null +++ b/{{cookiecutter.repository}}/tasks/clean.just @@ -0,0 +1,71 @@ +# run clean tasks +[group('clean')] +clean: clean-build clean-cache clean-constraints clean-coverage clean-docs clean-mlruns clean-mypy clean-outputs clean-pytest clean-python clean-requirements clean-ruff + +# clean build folders +[group('clean')] +clean-build: + rm -rf dist/ + rm -rf build/ + +# clean cache folder +[group('clean')] +clean-cache: + rm -rf .cache/ + +# clean constraints file +[group('clean')] +clean-constraints: + rm -rf constraints.txt + +# clean coverage files +[group('clean')] +clean-coverage: + rm -rf .coverage* + +# clean docs folder +[group('clean')] +clean-docs: + rm -rf docs/ + +# clean mlruns folder +[group('clean')] +clean-mlruns: + rm -rf mlruns/* + +# clean mypy folders +[group('clean')] +clean-mypy: + rm -rf .mypy_cache/ + +# clean outputs folder +[group('clean')] +clean-outputs: + rm -rf outputs/* + +# clean pytest cache +[group('clean')] +clean-pytest: + rm -rf .pytest_cache/ + +# clean python caches +[group('clean')] +clean-python: + find . -type f -name '*.py[co]' -delete + find . -type d -name __pycache__ -exec rm -r {} \+ + +# clean requirements file +[group('clean')] +clean-requirements: + rm -f requirements.txt + +# clean ruff cache +[group('clean')] +clean-ruff: + rm -rf .ruff_cache/ + +# clean venv folder +[confirm] +[group('clean')] +clean-venv: + rm -rf .venv/ diff --git a/{{cookiecutter.repository}}/tasks/cleans.py b/{{cookiecutter.repository}}/tasks/cleans.py deleted file mode 100644 index be82427..0000000 --- a/{{cookiecutter.repository}}/tasks/cleans.py +++ /dev/null @@ -1,105 +0,0 @@ -"""Clean tasks for pyinvoke.""" - -# %% IMPORTS - -from invoke.context import Context -from invoke.tasks import task - -# %% TASKS - -# %% - Tools - - -@task -def mypy(ctx: Context) -> None: - """Clean the mypy tool.""" - ctx.run("rm -rf .mypy_cache/") - - -@task -def ruff(ctx: Context) -> None: - """Clean the ruff tool.""" - ctx.run("rm -rf .ruff_cache/") - - -@task -def pytest(ctx: Context) -> None: - """Clean the pytest tool.""" - ctx.run("rm -rf .pytest_cache/") - - -@task -def coverage(ctx: Context) -> None: - """Clean the coverage tool.""" - ctx.run("rm -f .coverage*") - - -# %% - Folders - - -@task -def dist(ctx: Context) -> None: - """Clean the dist folder.""" - ctx.run("rm -f dist/*") - - -@task -def docs(ctx: Context) -> None: - """Clean the docs folder.""" - ctx.run("rm -rf docs/*") - - -@task -def cache(ctx: Context) -> None: - """Clean the cache folder.""" - ctx.run("rm -rf .cache/") - - -# %% - Sources - - -@task -def venv(ctx: Context) -> None: - """Clean the venv folder.""" - ctx.run("rm -rf .venv/") - - -@task -def poetry(ctx: Context) -> None: - """Clean poetry lock file.""" - ctx.run("rm -f poetry.lock") - - -@task -def python(ctx: Context) -> None: - """Clean python caches and bytecodes.""" - ctx.run("find . -type f -name '*.py[co]' -delete") - ctx.run(r"find . -type d -name __pycache__ -exec rm -r {} \+") - - -# %% - Combines - - -@task(pre=[mypy, ruff, pytest, coverage]) -def tools(_: Context) -> None: - """Run all tools tasks.""" - - -@task(pre=[dist, docs, cache]) -def folders(_: Context) -> None: - """Run all folders tasks.""" - - -@task(pre=[venv, poetry, python]) -def sources(_: Context) -> None: - """Run all sources tasks.""" - - -@task(pre=[tools, folders], default=True) -def all(_: Context) -> None: - """Run all tools and folders tasks.""" - - -@task(pre=[all, sources]) -def reset(_: Context) -> None: - """Run all tools, folders, sources, and projects tasks.""" diff --git a/{{cookiecutter.repository}}/tasks/commit.just b/{{cookiecutter.repository}}/tasks/commit.just new file mode 100644 index 0000000..5171dbe --- /dev/null +++ b/{{cookiecutter.repository}}/tasks/commit.just @@ -0,0 +1,14 @@ +# bump package +[group('commit')] +commit-bump: + uv run cz bump + +# commit package +[group('commit')] +commit-files: + uv run cz commit + +# get commit info +[group('commit')] +commit-info: + uv run cz info diff --git a/{{cookiecutter.repository}}/tasks/commits.py b/{{cookiecutter.repository}}/tasks/commits.py deleted file mode 100644 index 88b087b..0000000 --- a/{{cookiecutter.repository}}/tasks/commits.py +++ /dev/null @@ -1,31 +0,0 @@ -"""Commits tasks for pyinvoke.""" - -# %% IMPORTS - -from invoke.context import Context -from invoke.tasks import task - -# %% TASKS - - -@task -def info(ctx: Context) -> None: - """Print a guide for messages.""" - ctx.run("poetry run cz info") - - -@task -def bump(ctx: Context) -> None: - """Bump the version of the package.""" - ctx.run("poetry 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) - - -@task(pre=[commit], default=True) -def all(_: Context) -> None: - """Run all commit tasks.""" diff --git a/{{cookiecutter.repository}}/tasks/containers.py b/{{cookiecutter.repository}}/tasks/containers.py deleted file mode 100644 index d14152e..0000000 --- a/{{cookiecutter.repository}}/tasks/containers.py +++ /dev/null @@ -1,37 +0,0 @@ -"""Container tasks for pyinvoke.""" - -# %% IMPORTS - -from invoke.context import Context -from invoke.tasks import task - -from . import packages - -# %% CONFIGS - -IMAGE_TAG = "latest" - -# %% TASKS - - -@task -def compose(ctx: Context) -> None: - """Start up docker compose.""" - ctx.run("docker compose up --build") - - -@task(pre=[packages.build]) -def build(ctx: Context, tag: str = IMAGE_TAG) -> None: - """Build the container image.""" - ctx.run(f"docker build --tag={ctx.project.repository}:{tag} .") - - -@task -def run(ctx: Context, tag: str = IMAGE_TAG) -> None: - """Run the container image.""" - ctx.run(f"docker run --rm {ctx.project.repository}:{tag}") - - -@task(pre=[build, run], default=True) -def all(_: Context) -> None: - """Run all container tasks.""" diff --git a/{{cookiecutter.repository}}/tasks/doc.just b/{{cookiecutter.repository}}/tasks/doc.just new file mode 100644 index 0000000..9763649 --- /dev/null +++ b/{{cookiecutter.repository}}/tasks/doc.just @@ -0,0 +1,13 @@ +# run doc tasks +[group('doc')] +doc: doc-build + +# build documentation +[group('doc')] +doc-build format="google" output="docs": clean-docs + {% raw %}uv run pdoc --docformat={{format}} --output-directory={{output}} {{SOURCES}}/{{PACKAGE}}{% endraw %} + +# serve documentation +[group('doc')] +doc-serve format="google" port="8088": + {% raw %}uv run pdoc --docformat={{format}} --port={{port}} {{SOURCES}}/{{PACKAGE}}{% endraw %} diff --git a/{{cookiecutter.repository}}/tasks/docker.just b/{{cookiecutter.repository}}/tasks/docker.just new file mode 100644 index 0000000..d9d7440 --- /dev/null +++ b/{{cookiecutter.repository}}/tasks/docker.just @@ -0,0 +1,18 @@ +# run docker tasks +[group('docker')] +docker: docker-build docker-run + +# build docker image +[group('docker')] +docker-build tag="latest": package-build + {% raw %}docker build --tag={{REPOSITORY}}:{{tag}} .{% endraw %} + +# start docker compose +[group('docker')] +docker-compose: + docker compose up --build + +# run latest docker image +[group('docker')] +docker-run tag="latest": + {% raw %}docker run --rm {{REPOSITORY}}:{{tag}}{% endraw %} diff --git a/{{cookiecutter.repository}}/tasks/docs.py b/{{cookiecutter.repository}}/tasks/docs.py deleted file mode 100644 index 21eea9e..0000000 --- a/{{cookiecutter.repository}}/tasks/docs.py +++ /dev/null @@ -1,34 +0,0 @@ -"""Docs tasks for pyinvoke.""" - -# %% IMPORTS - -from invoke.context import Context -from invoke.tasks import task - -from . import cleans - -# %% CONFIGS - -DOC_FORMAT = "google" -OUTPUT_DIR = "docs/" - -# %% TASKS - - -@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}") - - -@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}" - ) - - -@task(pre=[cleans.docs, api], default=True) -def all(_: Context) -> None: - """Run all docs tasks.""" diff --git a/{{cookiecutter.repository}}/tasks/format.just b/{{cookiecutter.repository}}/tasks/format.just new file mode 100644 index 0000000..78bea80 --- /dev/null +++ b/{{cookiecutter.repository}}/tasks/format.just @@ -0,0 +1,13 @@ +# run format tasks +[group('format')] +format: format-import format-source + +# format code import +[group('format')] +format-import: + {% raw %}uv run ruff check --select=I --fix {{SOURCES}} {{TESTS}}{% endraw %} + +# format code source +[group('format')] +format-source: + {% raw %}uv run ruff format {{SOURCES}} {{TESTS}}{% endraw %} diff --git a/{{cookiecutter.repository}}/tasks/formats.py b/{{cookiecutter.repository}}/tasks/formats.py deleted file mode 100644 index 4631bae..0000000 --- a/{{cookiecutter.repository}}/tasks/formats.py +++ /dev/null @@ -1,25 +0,0 @@ -"""Format tasks for pyinvoke.""" - -# %% IMPORTS - -from invoke.context import Context -from invoke.tasks import task - -# %% TASKS - - -@task -def imports(ctx: Context) -> None: - """Format python imports with ruff.""" - ctx.run("poetry 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/") - - -@task(pre=[imports, sources], default=True) -def all(_: Context) -> None: - """Run all format tasks.""" diff --git a/{{cookiecutter.repository}}/tasks/install.just b/{{cookiecutter.repository}}/tasks/install.just new file mode 100644 index 0000000..e8a74df --- /dev/null +++ b/{{cookiecutter.repository}}/tasks/install.just @@ -0,0 +1,24 @@ +# run install tasks +[group('install')] +install: install-project install-hooks + +# install git hooks +[group('install')] +install-hooks: + uv run pre-commit install --hook-type=pre-push + uv run pre-commit install --hook-type=commit-msg + +# install the project +[group('install')] +install-project: + uv sync --all-groups + +# install github rulesets +[group('install')] +install-rulesets: + #!/usr/bin/env bash + set -euo pipefail + repo=$(gh repo view --json=name --jq=.name) + owner=$(gh repo view --json=owner --jq=.owner.login) + gh api --method POST -H "Accept: application/vnd.github+json" \ + "/repos/$owner/$repo/rulesets" --input=".github/rulesets/main.json" diff --git a/{{cookiecutter.repository}}/tasks/installs.py b/{{cookiecutter.repository}}/tasks/installs.py deleted file mode 100644 index 05669be..0000000 --- a/{{cookiecutter.repository}}/tasks/installs.py +++ /dev/null @@ -1,26 +0,0 @@ -"""Install tasks for pyinvoke.""" - -# %% IMPORTS - -from invoke.context import Context -from invoke.tasks import task - -# %% TASKS - - -@task -def poetry(ctx: Context) -> None: - """Install poetry packages.""" - ctx.run("poetry install") - - -@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") - - -@task(pre=[poetry, pre_commit], default=True) -def all(_: Context) -> None: - """Run all install tasks.""" diff --git a/{{cookiecutter.repository}}/tasks/package.just b/{{cookiecutter.repository}}/tasks/package.just new file mode 100644 index 0000000..765ca2b --- /dev/null +++ b/{{cookiecutter.repository}}/tasks/package.just @@ -0,0 +1,13 @@ +# run package tasks +[group('package')] +package: package-build + +# build package constraints +[group('package')] +package-constraints constraints="constraints.txt": + {% raw %}uv pip compile pyproject.toml --generate-hashes --output-file={{constraints}}{% endraw %} + +# build python package +[group('package')] +package-build constraints="constraints.txt": clean-build package-constraints + {% raw %}uv build --build-constraint={{constraints}} --require-hashes --wheel{% endraw %} diff --git a/{{cookiecutter.repository}}/tasks/packages.py b/{{cookiecutter.repository}}/tasks/packages.py deleted file mode 100644 index 38e9c3d..0000000 --- a/{{cookiecutter.repository}}/tasks/packages.py +++ /dev/null @@ -1,25 +0,0 @@ -"""Package tasks for pyinvoke.""" - -# %% IMPORTS - -from invoke.context import Context -from invoke.tasks import task - -from . import cleans - -# %% CONFIGS - -BUILD_FORMAT = "wheel" - -# %% TASKS - - -@task(pre=[cleans.dist]) -def build(ctx: Context, format: str = BUILD_FORMAT) -> None: - """Build the python package.""" - ctx.run(f"poetry build --format={format}") - - -@task(pre=[build], default=True) -def all(_: Context) -> None: - """Run all package tasks.""" diff --git a/{{cookiecutter.repository}}/tasks/project.just b/{{cookiecutter.repository}}/tasks/project.just new file mode 100644 index 0000000..76f278c --- /dev/null +++ b/{{cookiecutter.repository}}/tasks/project.just @@ -0,0 +1,9 @@ +# run project tasks +[group('project')] +project: project-requirements + +# export requirements file +[group('project')] +project-requirements: + uv export --format=requirements-txt --no-dev --no-hashes \ + --no-editable --no-emit-project --output-file=requirements.txt 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"]