diff --git a/README.md b/README.md index ccccfcb..d23adf9 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,9 @@ A python project template to simplify project setup. Adapted from https://github.com/fmind/cookiecutter-mlops-package -This template copy omits the MLFlow functionality. Use the linked mlops-package template if this is desired - The template provides a robust foundation for building, testing, packaging, and deploying Python packages and Docker Images. Adapt it to your project's needs; the source material is MLOps-focused but is suitable for a wide array of Python projects. -**Source resources**: +**Original resources**: - **[MLOps Coding Course (Learning)](https://mlops-coding-course.fmind.dev/)**: Learn how to create, develop, and maintain a state-of-the-art MLOps code base. - **[MLOps Python Package (Example)](https://github.com/fmind/mlops-python-package)**: Kickstart your MLOps initiative with a flexible, robust, and productive Python package. @@ -18,9 +16,13 @@ This template equips you with the essentials for creating, testing, and packagin You have the freedom to structure your `src/` and `tests/` directories according to your preferences. Alternatively, you can draw inspiration from the structure used in the [MLOps Python Package](https://github.com/fmind/mlops-python-package) project for a ready-made implementation. +## Applications + +This template includes a few optional application skeletons. See the nested README for details. + ## Key Features -(This section was copied into the created project's README so tool info is available to users.) +This section was copied into the created project's README so tool info is available. * **Streamlined Project Structure:** A well-defined directory layout for source code, tests, documentation, tasks, and Docker configurations. Uv Integration: Effortless dependency management and packaging with [uv](https://docs.astral.sh/uv/). @@ -30,6 +32,20 @@ Uv Integration: Effortless dependency management and packaging with [uv](https:/ * **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. +* Profiling: Several standard profilers are included for developers to choose from. Two popular call-stack profilers are [pyinstrument](https://github.com/joerick/pyinstrument) and [pyspy](https://github.com/benfred/py-spy). [memray](https://github.com/bloomberg/memray) is included for memory profiling. +* Load testing with [Locust](https://locust.io/). + +## Development + +### Checks + +This will run all checks on this cookiecutter repo (not just the project template) as specified in the `tasks/check.just` command: code quality, test coverage, unit tests, formatting, typing, and security. + +```shell +uv run just check +``` + +### Type checking ## Quick Start @@ -43,7 +59,7 @@ uv tool install cookiecutter cookiecutter gh:irod973/python-project-template ``` -You'll be prompted for the following variables: +You'll be prompted for the following variables. - `user`: Your GitHub username. - `name`: The name of your project. @@ -53,6 +69,10 @@ You'll be prompted for the following variables: - `version`: The initial version of your project. - `description`: A brief description of your project. - `python_version`: The Python version to use (e.g., 3.12). +- `include_fastapi`: Whether to include a sample FastAPI application. +- `include_metaflow`: Whether to include a sample Metaflow application. +- `include_torchvision`: Whether to include a sample Torchvision application. +- `include_package`: Whether to include a sample application for publishing a Python package. 2. **Initialize a git repository:** @@ -72,7 +92,7 @@ git init - `src/{{cookiecutter.package}}`: Your Python package source code. - `tests/`: Unit tests for your package. - `tasks/`: `just` commands for automation. -- `Dockerfile`: Configuration for building your Docker image. +- `docker/Dockerfile.python`: Configuration for building your Docker image. - `docker-compose.yml`: Orchestration file for running your project. 4. **Start developing!** @@ -89,16 +109,6 @@ Use the provided `just` commands to manage your development workflow: - `uv run just package`: Build your Python package. - `uv run just project`: Run the project in the CLI. -## Example Usage - -### Building and Running Your Docker Image - -```bash -invoke containers -``` - -This builds a Docker image based on your [`Dockerfile`](https://github.com/fmind/cookiecutter-mlops-package/blob/main/%7B%7Bcookiecutter.repository%7D%7D/Dockerfile) and runs it. - ## License The source material this is adapted from is licensed under the [MIT License](https://opensource.org/license/mit). See the [`LICENSE.txt`](https://github.com/fmind/cookiecutter-mlops-package/blob/main/LICENSE.txt) file for details. diff --git a/cookiecutter.json b/cookiecutter.json index f9d5241..48f5692 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -5,11 +5,12 @@ "package": "{{cookiecutter.repository.replace('-', '_')}}", "license": "MIT", "version": "0.1.0", - "description": "TODO", + "description": "TODO: Fill in description.", "python_version": "3.12", "include_fastapi": true, "include_metaflow": true, "include_package": true, + "include_torchvision": true, "coverage_threshold": 80, "__prompts__": { "user": "GitHub User", @@ -22,6 +23,7 @@ "include_fastapi": "Include FastAPI component? (y/n)", "include_metaflow": "Include Metaflow component? (y/n)", "include_package": "Include Python package component? (y/n)", + "include_torchvision": "Include TorchVision example component? (y/n)", "coverage_threshold": "Minimum code coverage percentage (cov-fail-under)" } } diff --git a/hooks/post_gen_project.py b/hooks/post_gen_project.py index 9c7afd3..0f98fbe 100644 --- a/hooks/post_gen_project.py +++ b/hooks/post_gen_project.py @@ -1,30 +1,27 @@ -import shutil +"""Remove any unwanted optional components after project generation.""" import os +import shutil -def is_false(val): - return str(val).strip().lower() in ("false", "n", "no", "0") + +def is_false(val: str) -> bool: + return val.strip().lower() in ("false", "n", "no", "0") # Remove FastAPI component if not included -if is_false('{{cookiecutter.include_fastapi}}'): - shutil.rmtree('src/fastapi_app', ignore_errors=True) +if is_false("{{cookiecutter.include_fastapi}}"): + shutil.rmtree("src/fastapi_app", ignore_errors=True) # Remove Metaflow component if not included -if is_false('{{cookiecutter.include_metaflow}}'): - shutil.rmtree('src/metaflow_app', ignore_errors=True) +if is_false("{{cookiecutter.include_metaflow}}"): + shutil.rmtree("src/metaflow_app", ignore_errors=True) # Remove Python package component if not included -if is_false('{{cookiecutter.include_package}}'): +if is_false("{{cookiecutter.include_package}}"): shutil.rmtree("src/{{cookiecutter.package}}", ignore_errors=True) # Remove publish workflow if present - workflow_path = os.path.join('.github', 'workflows', 'publish.yml') + workflow_path = os.path.join(".github", "workflows", "publish.yml") if os.path.exists(workflow_path): os.remove(workflow_path) - # Remove publish badge from README if present - readme_path = os.path.join('.', 'README.md') - if os.path.exists(readme_path): - with open(readme_path, 'r') as f: - lines = f.readlines() - with open(readme_path, 'w') as f: - for line in lines: - if 'publish.yml' not in line: - f.write(line) + +# Remove torchvision app if not included in template options +if is_false("{{cookiecutter.include_torchvision}}"): + shutil.rmtree("src/torchvision_app", ignore_errors=True) diff --git a/pyproject.toml b/pyproject.toml index 1083e03..52b1d1d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,3 +39,13 @@ update_changelog_on_bump = true [tool.pytest.ini_options] log_cli = true log_cli_level = "INFO" + +[tool.mypy] +pretty = true +python_version = "3.13" +check_untyped_defs = true +ignore_missing_imports = true +strict = false +disable_error_code = ["import-untyped"] +# See https://mypy.readthedocs.io/en/stable/config_file.html#untyped-definitions-and-calls +disallow_untyped_decorators = false diff --git a/tests/test_cookiecutter.py b/tests/test_cookiecutter.py index e2feb79..9f57d2e 100644 --- a/tests/test_cookiecutter.py +++ b/tests/test_cookiecutter.py @@ -31,11 +31,12 @@ def test_project_generation(cookies: Cookies) -> None: "license": "Apache-2.0", # Note: needs to be a "valid SPDX identifier" "version": "1.0.0", "description": "A test project.", - "python_version": "3.13", + "python_version": "3.12", "include_fastapi": "y", "include_metaflow": "y", "include_package": "y", - "coverage_threshold": "1" + "include_torchvision": "y", + "coverage_threshold": "1", } repository = context['name'].lower().replace(' ', '-') package = repository.replace('-', '_') @@ -58,8 +59,9 @@ def test_project_generation(cookies: Cookies) -> None: "python_version": context["python_version"], "include_fastapi": context["include_fastapi"], "include_metaflow": context["include_metaflow"], + "include_torchvision": context["include_torchvision"], "include_package": context["include_package"], - "coverage_threshold": context["coverage_threshold"] + "coverage_threshold": context["coverage_threshold"], } # - commands shell = Subprocess(cwd=result.project_path) diff --git a/{{cookiecutter.repository}}/Dockerfile b/{{cookiecutter.repository}}/Dockerfile deleted file mode 100644 index 9c6b6b7..0000000 --- a/{{cookiecutter.repository}}/Dockerfile +++ /dev/null @@ -1,25 +0,0 @@ -# https://docs.docker.com/engine/reference/builder/ -# https://docs.astral.sh/uv/guides/integration/docker/#intermediate-layers - -# Install uv -FROM python:{{cookiecutter.python_version}}-slim -COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ - -# Change the working directory to the `app` directory -WORKDIR /app - -# Install dependencies -RUN --mount=type=cache,target=/root/.cache/uv \ - --mount=type=bind,source=uv.lock,target=uv.lock \ - --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ - uv sync --locked --no-install-project - -# Copy the project into the image -ADD . /app - -# Sync the project -RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --locked - -# Default command (overridden by docker-compose) -CMD ["python", "--version"] diff --git a/{{cookiecutter.repository}}/README.md b/{{cookiecutter.repository}}/README.md index 99b6b16..d781d21 100644 --- a/{{cookiecutter.repository}}/README.md +++ b/{{cookiecutter.repository}}/README.md @@ -1,7 +1,9 @@ # {{cookiecutter.name}} [![check.yml](https://github.com/{{cookiecutter.user}}/{{cookiecutter.repository}}/actions/workflows/check.yml/badge.svg)](https://github.com/{{cookiecutter.user}}/{{cookiecutter.repository}}/actions/workflows/check.yml) +{% if cookiecutter.include_package %} [![publish.yml](https://github.com/{{cookiecutter.user}}/{{cookiecutter.repository}}/actions/workflows/publish.yml/badge.svg)](https://github.com/{{cookiecutter.user}}/{{cookiecutter.repository}}/actions/workflows/publish.yml) +{% endif %} [![Documentation](https://img.shields.io/badge/documentation-available-brightgreen.svg)](https://{{cookiecutter.user}}.github.io/{{cookiecutter.repository}}/) [![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) @@ -10,7 +12,7 @@ {{cookiecutter.description}}. -This README is generated from a cookiecutter template. Delete this comment and modify your README! +TODO: This README is generated from a cookiecutter template. Delete this comment and modify your README! # Installation @@ -21,21 +23,39 @@ uv run just install ``` # Usage -The provided template apps can be executed with the existing Docker templates. **Note:** Dependencies are not yet parametrized and need to be added using `uv add` +TODO: Fill in with your project's details. +{% if cookiecutter.include_torchvision %} +## Torchvision app + +This template includes an example torchvision app and associated dependencies. + +This has support for installing either CPU-only or CUDA 12.8 torch wheels via the `--extra {cpu, gpu}` uv sync argument. ```shell # Invoke docker compose -uv run just docker-compose - -# Or run with docker compose -docker compose up --build +uv run just docker-compose torchvision_app +``` +{% endif %} +{% if cookiecutter.include_metaflow %} +## Metaflow app -# 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 +This template includes an example Metaflow app and associated dependencies. +```shell +# Invoke docker compose +uv run just docker-compose metaflow_app ``` +{% endif %} +{% if cookiecutter.include_fastapi %} +## FastAPI + +This template includes an example FastAPI app and associated dependencies. +Note that this runs `fastapi dev` which includes auto-reload by default. This should be switched to `fastapi run` in production. +```shell +# Invoke docker compose +uv run just docker-compose fastapi_app +``` +{% endif %} ## Development Features * **Streamlined Project Structure:** A well-defined directory layout for source code, tests, documentation, tasks, and Docker configurations. diff --git a/{{cookiecutter.repository}}/docker-compose.yml b/{{cookiecutter.repository}}/docker-compose.yml index aeab110..0f07bd2 100644 --- a/{{cookiecutter.repository}}/docker-compose.yml +++ b/{{cookiecutter.repository}}/docker-compose.yml @@ -1,26 +1,62 @@ # https://docs.docker.com/compose/compose-file/ services: - {% if cookiecutter.include_fastapi %} + python: + build: + context: . + dockerfile: docker/Dockerfile.python + volumes: + - ./pyproject.toml:/app/pyproject.toml + - ./uv.lock:/app/uv.lock +{% if cookiecutter.include_fastapi %} fastapi_app: - build: . - command: ["uvicorn", "example_app.main:app", "--host", "0.0.0.0", "--reload"] + build: + context: . + dockerfile: docker/Dockerfile.python + args: + UV_SYNC_OPTIONS: "--group fastapi" + command: ["fastapi", "dev", "fastapi_app/main.py", "--host", "0.0.0.0"] ports: - "8000:8000" volumes: - - ./src:/app/src - {% endif %} - {% if cookiecutter.include_metaflow %} + - ./src/fastapi_app:/app/fastapi_app + - ./pyproject.toml:/app/pyproject.toml + - ./uv.lock:/app/uv.lock +{% endif %} +{% if cookiecutter.include_metaflow %} metaflow_app: - build: . + build: + context: . + dockerfile: docker/Dockerfile.python + args: + UV_SYNC_OPTIONS: "--group metaflow" command: ["python", "metaflow_app/spin_prototype.py"] volumes: - - ./src:/app/src - {% endif %} - {% if cookiecutter.include_package %} + - ./src/metaflow_app:/app/metaflow_app + - ./pyproject.toml:/app/pyproject.toml + - ./uv.lock:/app/uv.lock +{% endif %} +{% if cookiecutter.include_package %} python_package: - build: . + build: + context: . + dockerfile: docker/Dockerfile.python command: ["python", "-m", "{{cookiecutter.package}}"] volumes: - - ./src:/app/src - {% endif %} + - ./src/{{cookiecutter.package}}:/app/{{cookiecutter.package}} + - ./pyproject.toml:/app/pyproject.toml + - ./uv.lock:/app/uv.lock +{% endif %} +{% if cookiecutter.include_torchvision %} + torchvision_app: + build: + context: . + dockerfile: docker/Dockerfile.python + args: + UV_SYNC_OPTIONS: "--extra cpu" + command: ["python", "torchvision_app/torchvision_example.py"] + volumes: + - ./src/torchvision_app:/app/torchvision_app + - ./pyproject.toml:/app/pyproject.toml + - ./uv.lock:/app/uv.lock +{% endif %} diff --git a/{{cookiecutter.repository}}/docker/Dockerfile.python b/{{cookiecutter.repository}}/docker/Dockerfile.python new file mode 100644 index 0000000..744dd27 --- /dev/null +++ b/{{cookiecutter.repository}}/docker/Dockerfile.python @@ -0,0 +1,55 @@ +# An example using multi-stage image builds to create a final image without uv +# https://github.com/astral-sh/uv-docker-example/blob/main/multistage.Dockerfile + +# First, build the application in the `/app` directory. +# See `Dockerfile` for details. +FROM ghcr.io/astral-sh/uv:python{{cookiecutter.python_version}}-bookworm-slim AS builder +# Enable bytecode compilation; copy from the cache instead of linking since it's a mounted volume +ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy + +# Add any additional uv sync arguments +ARG UV_SYNC_OPTIONS + +# Disable Python downloads, because we want to use the system interpreter +# across both images. If using a managed Python version, it needs to be +# copied from the build image into the final image; see `standalone.Dockerfile` +# for an example. +ENV UV_PYTHON_DOWNLOADS=0 + +# Install the project's dependencies using the lockfile and settings +WORKDIR /app +RUN --mount=type=cache,target=/root/.cache/uv \ + --mount=type=bind,source=uv.lock,target=uv.lock \ + --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ + uv sync --locked --no-install-project --no-dev $UV_SYNC_OPTIONS + +# Then, add the rest of the project source code and install it +# Installing separately from its dependencies allows optimal layer caching +COPY . /app +RUN --mount=type=cache,target=/root/.cache/uv \ + uv sync --locked --no-dev $UV_SYNC_OPTIONS + +# Then, use a final image without uv +FROM python:{{cookiecutter.python_version}}-slim-bookworm +# It is important to use the image that matches the builder, as the path to the +# Python executable must be the same, e.g., using `python:3.11-slim-bookworm` +# will fail. + +# Setup a non-root user +RUN groupadd --system --gid 999 nonroot \ + && useradd --system --gid 999 --uid 999 --create-home nonroot + +# Copy the application from the builder +COPY --from=builder --chown=nonroot:nonroot /app /app + +# Place executables in the environment at the front of the path +ENV PATH="/app/.venv/bin:$PATH" + +# Use the non-root user to run our application +USER nonroot + +# Use `/app` as the working directory +WORKDIR /app + +# Baseline command, modify as needed +CMD ["python", "--version"] diff --git a/{{cookiecutter.repository}}/pyproject.toml b/{{cookiecutter.repository}}/pyproject.toml index 22763cf..5a8ee34 100644 --- a/{{cookiecutter.repository}}/pyproject.toml +++ b/{{cookiecutter.repository}}/pyproject.toml @@ -12,7 +12,7 @@ readme = "README.md" license = "{{cookiecutter.license}}" requires-python = ">={{cookiecutter.python_version}}" dependencies = [ - "hatchling>=1.27.0", + "loguru>=0.7.3", ] # LINKS @@ -20,7 +20,7 @@ dependencies = [ [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" +Repository = "https://github.com/{{cookiecutter.user}}/{{cookiecutter.repository}}" "Bug Tracker" = "https://github.com/{{cookiecutter.user}}/{{cookiecutter.repository}}/issues" # SCRIPTS @@ -43,18 +43,69 @@ check = [ ] commit = ["commitizen>=4.4.1", "pre-commit>=4.1.0"] dev =[ - "loguru>=0.7.3", "rust-just>=1.39.0", "locust>=2.31.8,<3.0.0", "pyinstrument>=4.7.3,<5.0.0", + "pyspy>=0.1.1", + ] doc = ["pdoc>=15.0.1"] +{% if cookiecutter.include_metaflow %} +metaflow = [ + "metaflow>=2.19.7", +] +{% endif %} +{% if cookiecutter.include_fastapi %} +fastapi = [ + "fastapi[standard]>=0.123.10", +] +{% endif %} # TOOLS [tool.uv] default-groups = ["check", "commit", "dev", "doc"] +{% if cookiecutter.include_torchvision %} +# torch +# enable selecting torch wheel based on environment with uv sync --extra {cpu, gpu} +# https://docs.astral.sh/uv/guides/integration/pytorch/#configuring-accelerators-with-optional-dependencies +conflicts = [ + [ + { extra = "cpu" }, + { extra = "gpu" }, + ], +] + +[project.optional-dependencies] +cpu = [ + "torch>=2.7.0", + "torchvision>=0.22.0", +] +gpu = [ + "torch>=2.7.0", + "torchvision>=0.22.0", +] + +[tool.uv.sources] +torch = [ + { index = "pytorch-cpu", extra = "cpu" }, + { index = "pytorch-cu128", extra = "gpu" }, +] +torchvision = [ + { index = "pytorch-cpu", extra = "cpu" }, + { index = "pytorch-cu128", extra = "gpu" }, +] + +[[tool.uv.index]] +name = "pytorch-cpu" +url = "https://download.pytorch.org/whl/cpu" +explicit = true +[[tool.uv.index]] +name = "pytorch-cu128" +url = "https://download.pytorch.org/whl/cu128" +explicit = true +{% endif %} [tool.bandit] targets = ["src"] @@ -106,5 +157,5 @@ convention = "google" # SYSTEMS [build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" +requires = ["uv_build>=0.9.16,<0.10.0"] +build-backend = "uv_build" diff --git a/{{cookiecutter.repository}}/src/metaflow_app/spin_prototype.py b/{{cookiecutter.repository}}/src/metaflow_app/spin_prototype.py index ec1c171..60784bb 100644 --- a/{{cookiecutter.repository}}/src/metaflow_app/spin_prototype.py +++ b/{{cookiecutter.repository}}/src/metaflow_app/spin_prototype.py @@ -28,7 +28,7 @@ def start(self): from io import StringIO self.rows = [] - for row in csv.reader(StringIO(self.movie_data), delimiter=","): + for row in csv.reader(StringIO(self.movie_data), delimiter=","): # type: ignore logger.info(f"{row=}") self.rows.append(row) diff --git a/{{cookiecutter.repository}}/src/torchvision_app/__init__.py b/{{cookiecutter.repository}}/src/torchvision_app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/{{cookiecutter.repository}}/src/torchvision_app/assets/astronaut.jpg b/{{cookiecutter.repository}}/src/torchvision_app/assets/astronaut.jpg new file mode 100644 index 0000000..9716f65 Binary files /dev/null and b/{{cookiecutter.repository}}/src/torchvision_app/assets/astronaut.jpg differ diff --git a/{{cookiecutter.repository}}/src/torchvision_app/torchvision_example.py b/{{cookiecutter.repository}}/src/torchvision_app/torchvision_example.py new file mode 100644 index 0000000..f27ac6b --- /dev/null +++ b/{{cookiecutter.repository}}/src/torchvision_app/torchvision_example.py @@ -0,0 +1,23 @@ +from pathlib import Path + +import torch +from loguru import logger +from torchvision.io import decode_image +from torchvision.transforms import v2 + +torch.manual_seed(1) + +def main(): + img = decode_image(str(Path(__file__).parent / "assets/astronaut.jpg")) + logger.info(f"{type(img)=}, {img.dtype=}, {img.shape=}") + transforms = v2.Compose([ + v2.RandomResizedCrop(size=(224, 224), antialias=True), + v2.RandomHorizontalFlip(p=0.5), + v2.ToDtype(torch.float32, scale=True), + v2.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), + ]) + out = transforms(img) + logger.info(f"{type(out)=}, {out.dtype=}, {out.shape=}") + +if __name__ == "__main__": + main() diff --git a/{{cookiecutter.repository}}/tasks/docker.just b/{{cookiecutter.repository}}/tasks/docker.just index d9d7440..2829dcc 100644 --- a/{{cookiecutter.repository}}/tasks/docker.just +++ b/{{cookiecutter.repository}}/tasks/docker.just @@ -5,12 +5,12 @@ docker: docker-build docker-run # build docker image [group('docker')] docker-build tag="latest": package-build - {% raw %}docker build --tag={{REPOSITORY}}:{{tag}} .{% endraw %} + {% raw %}docker build --tag={{REPOSITORY}}:{{tag}} -f docker/Dockerfile.python .{% endraw %} # start docker compose [group('docker')] -docker-compose: - docker compose up --build +docker-compose-service service: + docker compose up --build {% raw %}{{service}}{% endraw %} # run latest docker image [group('docker')] diff --git a/{{cookiecutter.repository}}/tasks/package.just b/{{cookiecutter.repository}}/tasks/package.just index 765ca2b..4c83e86 100644 --- a/{{cookiecutter.repository}}/tasks/package.just +++ b/{{cookiecutter.repository}}/tasks/package.just @@ -10,4 +10,4 @@ package-constraints constraints="constraints.txt": # build python package [group('package')] package-build constraints="constraints.txt": clean-build package-constraints - {% raw %}uv build --build-constraint={{constraints}} --require-hashes --wheel{% endraw %} + {% raw %}uv build --build-constraint={{constraints}} --wheel{% endraw %}