From 22ec23dc9353e221ba0a0a4ac6762da4e3a560c7 Mon Sep 17 00:00:00 2001 From: Laurent Sorber Date: Fri, 5 Apr 2024 10:43:13 +0200 Subject: [PATCH 01/17] feat!: make package/app choice explicit (#222) --- .github/workflows/test.yml | 26 ++-- README.md | 44 +++---- cookiecutter.json | 34 ++--- hooks/post_gen_project.py | 28 ++--- .../__init__.py | 1 - .../api.py | 24 ---- .../app.py | 7 -- .../cli.py | 11 -- .../sentry.py | 17 --- .../tests/__init__.py | 1 - .../tests/test_cli.py | 15 --- .../tests/test_import.py | 8 -- .../tests/test_sentry.py | 11 -- .../.devcontainer/devcontainer.json | 4 +- .../.dockerignore | 0 .../.github/dependabot.yml | 0 .../.github/workflows/deploy.yml | 0 .../.github/workflows/publish.yml | 0 .../.github/workflows/test.yml | 6 +- .../.gitignore | 0 .../.gitlab-ci.yml | 12 +- .../.pre-commit-config.yaml | 0 .../Dockerfile | 28 ++--- .../README.md | 60 +++++++-- .../docker-compose.yml | 8 +- .../pyproject.toml | 117 +++++++----------- .../__init__.py | 1 + .../api.py | 36 ++++++ .../cli.py | 12 ++ .../py.typed | 0 .../tests/__init__.py | 1 + .../tests/test_api.py | 6 +- .../tests/test_cli.py | 15 +++ .../tests/test_import.py | 8 ++ 34 files changed, 259 insertions(+), 282 deletions(-) delete mode 100644 {{ cookiecutter.__package_name_kebab_case }}/src/{{ cookiecutter.__package_name_snake_case }}/__init__.py delete mode 100644 {{ cookiecutter.__package_name_kebab_case }}/src/{{ cookiecutter.__package_name_snake_case }}/api.py delete mode 100644 {{ cookiecutter.__package_name_kebab_case }}/src/{{ cookiecutter.__package_name_snake_case }}/app.py delete mode 100644 {{ cookiecutter.__package_name_kebab_case }}/src/{{ cookiecutter.__package_name_snake_case }}/cli.py delete mode 100644 {{ cookiecutter.__package_name_kebab_case }}/src/{{ cookiecutter.__package_name_snake_case }}/sentry.py delete mode 100644 {{ cookiecutter.__package_name_kebab_case }}/tests/__init__.py delete mode 100644 {{ cookiecutter.__package_name_kebab_case }}/tests/test_cli.py delete mode 100644 {{ cookiecutter.__package_name_kebab_case }}/tests/test_import.py delete mode 100644 {{ cookiecutter.__package_name_kebab_case }}/tests/test_sentry.py rename {{{ cookiecutter.__package_name_kebab_case }} => {{ cookiecutter.__project_name_kebab_case }}}/.devcontainer/devcontainer.json (95%) rename {{{ cookiecutter.__package_name_kebab_case }} => {{ cookiecutter.__project_name_kebab_case }}}/.dockerignore (100%) rename {{{ cookiecutter.__package_name_kebab_case }} => {{ cookiecutter.__project_name_kebab_case }}}/.github/dependabot.yml (100%) rename {{{ cookiecutter.__package_name_kebab_case }} => {{ cookiecutter.__project_name_kebab_case }}}/.github/workflows/deploy.yml (100%) rename {{{ cookiecutter.__package_name_kebab_case }} => {{ cookiecutter.__project_name_kebab_case }}}/.github/workflows/publish.yml (100%) rename {{{ cookiecutter.__package_name_kebab_case }} => {{ cookiecutter.__project_name_kebab_case }}}/.github/workflows/test.yml (90%) rename {{{ cookiecutter.__package_name_kebab_case }} => {{ cookiecutter.__project_name_kebab_case }}}/.gitignore (100%) rename {{{ cookiecutter.__package_name_kebab_case }} => {{ cookiecutter.__project_name_kebab_case }}}/.gitlab-ci.yml (92%) rename {{{ cookiecutter.__package_name_kebab_case }} => {{ cookiecutter.__project_name_kebab_case }}}/.pre-commit-config.yaml (100%) rename {{{ cookiecutter.__package_name_kebab_case }} => {{ cookiecutter.__project_name_kebab_case }}}/Dockerfile (85%) rename {{{ cookiecutter.__package_name_kebab_case }} => {{ cookiecutter.__project_name_kebab_case }}}/README.md (80%) rename {{{ cookiecutter.__package_name_kebab_case }} => {{ cookiecutter.__project_name_kebab_case }}}/docker-compose.yml (76%) rename {{{ cookiecutter.__package_name_kebab_case }} => {{ cookiecutter.__project_name_kebab_case }}}/pyproject.toml (71%) create mode 100644 {{ cookiecutter.__project_name_kebab_case }}/src/{{ cookiecutter.__project_name_snake_case }}/__init__.py create mode 100644 {{ cookiecutter.__project_name_kebab_case }}/src/{{ cookiecutter.__project_name_snake_case }}/api.py create mode 100644 {{ cookiecutter.__project_name_kebab_case }}/src/{{ cookiecutter.__project_name_snake_case }}/cli.py rename {{{ cookiecutter.__package_name_kebab_case }}/src/{{ cookiecutter.__package_name_snake_case }} => {{ cookiecutter.__project_name_kebab_case }}/src/{{ cookiecutter.__project_name_snake_case }}}/py.typed (100%) create mode 100644 {{ cookiecutter.__project_name_kebab_case }}/tests/__init__.py rename {{{ cookiecutter.__package_name_kebab_case }} => {{ cookiecutter.__project_name_kebab_case }}}/tests/test_api.py (56%) create mode 100644 {{ cookiecutter.__project_name_kebab_case }}/tests/test_cli.py create mode 100644 {{ cookiecutter.__project_name_kebab_case }}/tests/test_import.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bed85160..0188a3be 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,9 +13,10 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.8", "3.10"] + python-version: ["3.9", "3.12"] + project-type: ["app", "package"] - name: Python ${{ matrix.python-version }} + name: Python ${{ matrix.python-version }} ${{ matrix.project-type }} steps: - name: Checkout @@ -26,12 +27,12 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: "3.8" + python-version: "3.9" - - name: Scaffold Python package + - name: Scaffold Python project run: | pip install --no-input cruft - cruft create --no-input --extra-context '{"package_name": "My Package", "python_version": "3.8", "docker_image":"radixai/python-gpu:$PYTHON_VERSION-cuda11.8", "with_fastapi_api": "1", "with_typer_cli": "1"}' ./template/ + cruft create --no-input --extra-context '{"project_type": "${{ matrix.project-type }}", "project_name": "My Project", "python_version": "3.9", "__docker_image":"radixai/python-gpu:$PYTHON_VERSION-cuda11.8", "with_fastapi_api": "1", "with_typer_cli": "1"}' ./template/ - name: Set up Node.js uses: actions/setup-node@v4 @@ -39,7 +40,7 @@ jobs: node-version: 21 - name: Install @devcontainers/cli - run: npm install --location=global @devcontainers/cli@0.55.0 + run: npm install --location=global @devcontainers/cli@0.58.0 - name: Start Dev Container with Python ${{ matrix.python-version }} run: | @@ -48,19 +49,20 @@ jobs: git checkout -b test git add . PYTHON_VERSION=${{ matrix.python-version }} devcontainer up --workspace-folder . - working-directory: ./my-package/ + working-directory: ./my-project/ - - name: Lint package - run: devcontainer exec --workspace-folder my-package poe lint + - name: Lint project + run: devcontainer exec --workspace-folder my-project poe lint - - name: Test package - run: devcontainer exec --workspace-folder my-package poe test + - name: Test project + run: devcontainer exec --workspace-folder my-project poe test - name: Build app Docker image + if: ${{ matrix.project-type == 'app' }} uses: docker/build-push-action@v5 with: build-args: | SOURCE_BRANCH=${{ env.GITHUB_REF }} SOURCE_COMMIT=${{ env.GITHUB_SHA }} - context: ./my-package/ + context: ./my-project/ target: app diff --git a/README.md b/README.md index 08e86767..9406228b 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,9 @@ A modern [Cookiecutter](https://github.com/cookiecutter/cookiecutter) template f ## 🍿 Demo -See [My Package](https://github.com/radix-ai/my-package) for an example of a Python package and app that is scaffolded with this template. +See [Conformal Tights](https://github.com/radix-ai/conformal-tights) for an example of a Python package that is scaffolded with this template. -Starting development in My Package can be done with a single click by [opening My Package in GitHub Codespaces](https://github.com/codespaces/new?hide_repo_select=true&ref=main&repo=450509735), or [opening My Package in a Dev Container](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/radix-ai/my-package). +Starting development can be done with a single click by [opening the package in GitHub Codespaces](https://github.com/codespaces/new?hide_repo_select=true&ref=main&repo=765698489&skip_quickstart=true), or [opening the package in a Dev Container](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/radix-ai/conformal-tights). ## 🎁 Features @@ -42,9 +42,9 @@ To create a new Python project with this template: ```sh cruft create -f https://github.com/radix-ai/poetry-cookiecutter ``` -4. _Optional:_ if your repository name differs from your project's slugified package name (see `package_name` in the [Template parameters](https://github.com/radix-ai/poetry-cookiecutter#-template-parameters) below), you will need to copy the scaffolded project into the repository with: +4. _Optional:_ if your repository name differs from your project's slugified name (see `project_name` in the [Template parameters](https://github.com/radix-ai/poetry-cookiecutter#-template-parameters) below), you will need to copy the scaffolded project into the repository with: ```sh - cp -r {package-name}/ {repository-name}/ + cp -r {project-name}/ {repository-name}/ ``` ### Updating your Python project @@ -56,23 +56,19 @@ To update your Python project with the latest template: ## 🤓 Template parameters -| Parameter | Description | -| ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `package_name`
"Spline Reticulator" | The name of the package. Will be slugified to `snake_case` for importing and `kebab-case` for installing. For example, `My Package` will be `my_package` for importing and `my-package` for installing. | -| `package_description`
"A Python package that reticulates splines." | A single-line description of the package. | -| `package_url`
"https://github.com/user/spline-reticulator" | The URL to the package's repository. | -| `author_name`
"John Smith" | The full name of the primary author of the package. | -| `author_email`
"john@example.com" | The email address of the primary author of the package. | -| `python_version`
"3.8" | The minimum Python version that the package requires. | -| `docker_image`
"python:$PYTHON_VERSION-slim" | The base Docker image to use for the Dev Container and application. The $PYTHON_VERSION build argument is equal to the `python_version` value by default, but may be overridden when building the image to test different Python versions. If CUDA support is required, you may use [radixai/python-gpu:$PYTHON_VERSION-cuda11.8](https://github.com/radix-ai/python-gpu). | -| `development_environment`
["simple", "strict"] | Whether to configure the development environment with a focus on simplicity or with a focus on strictness. In strict mode, additional [Ruff rules](https://beta.ruff.rs/docs/rules/) are added, and tools such as [Mypy](https://github.com/python/mypy) and [Pytest](https://github.com/pytest-dev/pytest) are set to strict mode. | -| `with_conventional_commits`
["0", "1"] | If "1", [Commitizen](https://github.com/commitizen-tools/commitizen) will verify that your commits follow the [Conventional Commits](https://www.conventionalcommits.org/) standard. In return, `cz bump` may be used to automate [Semantic Versioning](https://semver.org/) and [Keep A Changelog](https://keepachangelog.com/). | -| `with_fastapi_api`
["0", "1"] | If "1", [FastAPI](https://github.com/tiangolo/fastapi) is added as a run time dependency, FastAPI API stubs and tests are added, a `poe api` command for serving the API is added, and an `app` stage that packages the API is added to the Dockerfile. Additionally, the CI workflow will push the application as a Docker image instead of publishing the Python package. | -| `with_pydantic_typing`
["0", "1"] | If "1", [Pydantic](https://github.com/samuelcolvin/pydantic) is added as a run time dependency, and the [Pydantic mypy plugin](https://pydantic-docs.helpmanual.io/mypy_plugin/) is enabled and configured. | -| `with_sentry_logging`
["0", "1"] | If "1", [Sentry](https://github.com/getsentry/sentry-python) is added as a run time dependency, and a Sentry configuration stub and tests are added. | -| `with_streamlit_app`
["0", "1"] | If "1", [Streamlit](https://github.com/streamlit/streamlit) is added as a run time dependency, a Streamlit application stub is added, a `poe app` command to serve the Streamlit app is added, and an `app` stage that packages the Streamlit app is added to the Dockerfile. Additionally, the CI workflow will push the application as a Docker image instead of publishing the Python package. | -| `with_typer_cli`
["0", "1"] | If "1", [Typer](https://github.com/tiangolo/typer) is added as a run time dependency, Typer CLI stubs and tests are added, the package itself is registered as a CLI, and an `app` stage is added to the Dockerfile that packages the CLI. | -| `continuous_integration`
["GitHub", "GitLab"] | Whether to include a [GitHub Actions](https://docs.github.com/en/actions) or a [GitLab CI/CD](https://docs.gitlab.com/ee/ci/) continuous integration workflow for testing and publishing the package or app. | -| `docstring_style`
["NumPy", "Google"] | Whether to use and validate [NumPy-style](https://numpydoc.readthedocs.io/en/latest/format.html) or [Google-style docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings). | -| `private_package_repository_name`
"Private Package Repository" | Optional name of a private package repository to install packages from and publish this package to. | -| `private_package_repository_url`
"https://pypi.example.com/simple" | Optional URL of a private package repository to install packages from and publish this package to. Make sure to include the `/simple` suffix. For instance, when using a GitLab Package Registry this value should be of the form `https://gitlab.com/api/v4/projects/` `{project_id}` `/packages/pypi/simple`. | +| Parameter | Description | +| ----------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `project_type`
["package", "app"] | Whether the project is a publishable Python package or a deployable Python app. | +| `project_name`
"Spline Reticulator" | The name of the project. Will be slugified to `snake_case` for importing and `kebab-case` for installing. For example, `My Package` will be `my_package` for importing and `my-package` for installing. | +| `project_description`
"A Python package that reticulates splines." | A single-line description of the project. | +| `project_url`
"https://github.com/user/spline-reticulator" | The URL to the project's repository. | +| `author_name`
"John Smith" | The full name of the primary author of the project. | +| `author_email`
"john@example.com" | The email address of the primary author of the project. | +| `python_version`
"3.10" | The minimum Python version that the project requires. | +| `development_environment`
["simple", "strict"] | Whether to configure the development environment with a focus on simplicity or with a focus on strictness. In strict mode, additional [Ruff rules](https://beta.ruff.rs/docs/rules/) are added, and tools such as [Mypy](https://github.com/python/mypy) and [Pytest](https://github.com/pytest-dev/pytest) are set to strict mode. | +| `with_conventional_commits`
["0", "1"] | If "1", [Commitizen](https://github.com/commitizen-tools/commitizen) will verify that your commits follow the [Conventional Commits](https://www.conventionalcommits.org/) standard. In return, `cz bump` may be used to automate [Semantic Versioning](https://semver.org/) and [Keep A Changelog](https://keepachangelog.com/). | +| `with_fastapi_api`
["0", "1"] | If "1", [FastAPI](https://github.com/tiangolo/fastapi) is added as a run time dependency, FastAPI API stubs and tests are added, a `poe api` command for serving the API is added. | +| `with_typer_cli`
["0", "1"] | If "1", [Typer](https://github.com/tiangolo/typer) is added as a run time dependency, Typer CLI stubs and tests are added, the package itself is registered as a CLI. | +| `continuous_integration`
["GitHub", "GitLab"] | Whether to include a [GitHub Actions](https://docs.github.com/en/actions) or a [GitLab CI/CD](https://docs.gitlab.com/ee/ci/) continuous integration workflow for testing the project, and publishing the package or deploying the app. | +| `private_package_repository_name`
"Private Package Repository" | Optional name of a private package repository to install packages from and publish this package to. | +| `private_package_repository_url`
"https://pypi.example.com/simple" | Optional URL of a private package repository to install packages from and publish this package to. Make sure to include the `/simple` suffix. For instance, when using a GitLab Package Registry this value should be of the form `https://gitlab.com/api/v4/projects/` `{project_id}` `/packages/pypi/simple`. | diff --git a/cookiecutter.json b/cookiecutter.json index d958e29b..f548f9a9 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -1,23 +1,29 @@ { - "package_name": "My Package", - "package_description": "A Python package that ...", - "package_url": "https://github.com/user/my-package", + "project_type": [ + "package", + "app" + ], + "project_name": "{% if cookiecutter.project_type == 'app' %}My App{% else %}My Package{% endif %}", + "project_description": "A Python {{ cookiecutter.project_type }} that reticulates splines.", + "project_url": "https://github.com/user/my-{{ cookiecutter.project_type }}", "author_name": "John Smith", "author_email": "john@example.com", - "python_version": "3.8", - "docker_image": "python:$PYTHON_VERSION-slim", - "development_environment": ["simple", "strict"], + "python_version": "{% if cookiecutter.project_type == 'app' %}3.12{% else %}3.10{% endif %}", + "development_environment": [ + "simple", + "strict" + ], "with_conventional_commits": "{% if cookiecutter.development_environment == 'simple' %}0{% else %}1{% endif %}", "with_fastapi_api": "0", - "with_jupyter_lab": "0", - "with_pydantic_typing": "0", - "with_sentry_logging": "0", - "with_streamlit_app": "0", "with_typer_cli": "0", - "continuous_integration": ["GitHub", "GitLab"], - "docstring_style": ["NumPy", "Google"], + "continuous_integration": [ + "GitHub", + "GitLab" + ], "private_package_repository_name": "", "private_package_repository_url": "", - "__package_name_kebab_case": "{{ cookiecutter.package_name|slugify }}", - "__package_name_snake_case": "{{ cookiecutter.package_name|slugify(separator='_') }}" + "__docker_image": "python:$PYTHON_VERSION-slim", + "__docstring_style": "NumPy", + "__project_name_kebab_case": "{{ cookiecutter.project_name|slugify }}", + "__project_name_snake_case": "{{ cookiecutter.project_name|slugify(separator='_') }}" } \ No newline at end of file diff --git a/hooks/post_gen_project.py b/hooks/post_gen_project.py index 396618b0..91428943 100644 --- a/hooks/post_gen_project.py +++ b/hooks/post_gen_project.py @@ -2,38 +2,26 @@ import shutil # Read Cookiecutter configuration. -package_name = "{{ cookiecutter.__package_name_snake_case }}" +project_name = "{{ cookiecutter.__project_name_snake_case }}" development_environment = "{{ cookiecutter.development_environment }}" with_fastapi_api = int("{{ cookiecutter.with_fastapi_api }}") -with_sentry_logging = int("{{ cookiecutter.with_sentry_logging }}") -with_streamlit_app = int("{{ cookiecutter.with_streamlit_app }}") with_typer_cli = int("{{ cookiecutter.with_typer_cli }}") continuous_integration = "{{ cookiecutter.continuous_integration }}" -is_deployable_app = "{{ not not cookiecutter.with_fastapi_api|int or not not cookiecutter.with_streamlit_app|int }}" == "True" -is_publishable_package = "{{ not cookiecutter.with_fastapi_api|int and not cookiecutter.with_streamlit_app|int }}" == "True" +is_application = "{{ cookiecutter.project_type == 'app' }}" == "True" # Remove py.typed and Dependabot if not in strict mode. if development_environment != "strict": - os.remove(f"src/{package_name}/py.typed") + os.remove(f"src/{project_name}/py.typed") os.remove(".github/dependabot.yml") # Remove FastAPI if not selected. if not with_fastapi_api: - os.remove(f"src/{package_name}/api.py") + os.remove(f"src/{project_name}/api.py") os.remove("tests/test_api.py") -# Remove Sentry if not selected. -if not with_sentry_logging: - os.remove(f"src/{package_name}/sentry.py") - os.remove("tests/test_sentry.py") - -# Remove Streamlit if not selected. -if not with_streamlit_app: - os.remove(f"src/{package_name}/app.py") - # Remove Typer if not selected. if not with_typer_cli: - os.remove(f"src/{package_name}/cli.py") + os.remove(f"src/{project_name}/cli.py") os.remove("tests/test_cli.py") # Remove the continuous integration provider that is not selected. @@ -44,7 +32,7 @@ # Remove unused GitHub Actions workflows. if continuous_integration == "GitHub": - if not is_deployable_app: - os.remove(".github/workflows/deploy.yml") - if not is_publishable_package: + if is_application: os.remove(".github/workflows/publish.yml") + else: + os.remove(".github/workflows/deploy.yml") diff --git a/{{ cookiecutter.__package_name_kebab_case }}/src/{{ cookiecutter.__package_name_snake_case }}/__init__.py b/{{ cookiecutter.__package_name_kebab_case }}/src/{{ cookiecutter.__package_name_snake_case }}/__init__.py deleted file mode 100644 index 5475aa88..00000000 --- a/{{ cookiecutter.__package_name_kebab_case }}/src/{{ cookiecutter.__package_name_snake_case }}/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""{{ cookiecutter.package_name }} package.""" diff --git a/{{ cookiecutter.__package_name_kebab_case }}/src/{{ cookiecutter.__package_name_snake_case }}/api.py b/{{ cookiecutter.__package_name_kebab_case }}/src/{{ cookiecutter.__package_name_snake_case }}/api.py deleted file mode 100644 index 74c19ffc..00000000 --- a/{{ cookiecutter.__package_name_kebab_case }}/src/{{ cookiecutter.__package_name_snake_case }}/api.py +++ /dev/null @@ -1,24 +0,0 @@ -"""{{ cookiecutter.package_name }} REST API.""" - -import logging - -import coloredlogs -from fastapi import FastAPI - -app = FastAPI() - - -@app.on_event("startup") -def startup_event() -> None: - """Run API startup events.""" - # Remove all handlers associated with the root logger object. - for handler in logging.root.handlers: - logging.root.removeHandler(handler) - # Add coloredlogs' coloured StreamHandler to the root logger. - coloredlogs.install() - - -@app.get("/") -def read_root() -> str: - """Read root.""" - return "Hello world" diff --git a/{{ cookiecutter.__package_name_kebab_case }}/src/{{ cookiecutter.__package_name_snake_case }}/app.py b/{{ cookiecutter.__package_name_kebab_case }}/src/{{ cookiecutter.__package_name_snake_case }}/app.py deleted file mode 100644 index 16e3c663..00000000 --- a/{{ cookiecutter.__package_name_kebab_case }}/src/{{ cookiecutter.__package_name_snake_case }}/app.py +++ /dev/null @@ -1,7 +0,0 @@ -"""Streamlit app.""" - -from importlib.metadata import version - -import streamlit as st - -st.title(f"{{ cookiecutter.package_name }} v{version('{{ cookiecutter.__package_name_kebab_case }}')}") # type: ignore[no-untyped-call] diff --git a/{{ cookiecutter.__package_name_kebab_case }}/src/{{ cookiecutter.__package_name_snake_case }}/cli.py b/{{ cookiecutter.__package_name_kebab_case }}/src/{{ cookiecutter.__package_name_snake_case }}/cli.py deleted file mode 100644 index 7550cf5b..00000000 --- a/{{ cookiecutter.__package_name_kebab_case }}/src/{{ cookiecutter.__package_name_snake_case }}/cli.py +++ /dev/null @@ -1,11 +0,0 @@ -"""{{ cookiecutter.package_name }} CLI.""" - -import typer - -app = typer.Typer() - - -@app.command() -def say(message: str = "") -> None: - """Say a message.""" - typer.echo(message) diff --git a/{{ cookiecutter.__package_name_kebab_case }}/src/{{ cookiecutter.__package_name_snake_case }}/sentry.py b/{{ cookiecutter.__package_name_kebab_case }}/src/{{ cookiecutter.__package_name_snake_case }}/sentry.py deleted file mode 100644 index 1251c73a..00000000 --- a/{{ cookiecutter.__package_name_kebab_case }}/src/{{ cookiecutter.__package_name_snake_case }}/sentry.py +++ /dev/null @@ -1,17 +0,0 @@ -"""Sentry configuration.""" - -import os -from importlib.metadata import version - -import sentry_sdk - - -def configure_sentry() -> None: - """Configure Sentry.""" - environment = os.environ.get("ENVIRONMENT", "local") - sentry_sdk.init( - dsn=os.environ["SENTRY_DSN"], - traces_sample_rate=0.1 if environment != "production" else 0.001, - release=f"{{ cookiecutter.__package_name_kebab_case }}@{version('{{ cookiecutter.__package_name_kebab_case }}')}", - environment=environment, - ) diff --git a/{{ cookiecutter.__package_name_kebab_case }}/tests/__init__.py b/{{ cookiecutter.__package_name_kebab_case }}/tests/__init__.py deleted file mode 100644 index 03f8772d..00000000 --- a/{{ cookiecutter.__package_name_kebab_case }}/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""{{ cookiecutter.package_name }} test suite.""" diff --git a/{{ cookiecutter.__package_name_kebab_case }}/tests/test_cli.py b/{{ cookiecutter.__package_name_kebab_case }}/tests/test_cli.py deleted file mode 100644 index 23778096..00000000 --- a/{{ cookiecutter.__package_name_kebab_case }}/tests/test_cli.py +++ /dev/null @@ -1,15 +0,0 @@ -"""Test {{ cookiecutter.package_name }} CLI.""" - -from typer.testing import CliRunner - -from {{ cookiecutter.__package_name_snake_case }}.cli import app - -runner = CliRunner() - - -def test_say() -> None: - """Test that the say command works as expected.""" - message = "Hello world" - result = runner.invoke(app, ["--message", message]) - assert result.exit_code == 0 - assert message in result.stdout diff --git a/{{ cookiecutter.__package_name_kebab_case }}/tests/test_import.py b/{{ cookiecutter.__package_name_kebab_case }}/tests/test_import.py deleted file mode 100644 index bb593691..00000000 --- a/{{ cookiecutter.__package_name_kebab_case }}/tests/test_import.py +++ /dev/null @@ -1,8 +0,0 @@ -"""Test {{ cookiecutter.package_name }}.""" - -import {{ cookiecutter.__package_name_snake_case }} - - -def test_import() -> None: - """Test that the package can be imported.""" - assert isinstance({{ cookiecutter.__package_name_snake_case }}.__name__, str) diff --git a/{{ cookiecutter.__package_name_kebab_case }}/tests/test_sentry.py b/{{ cookiecutter.__package_name_kebab_case }}/tests/test_sentry.py deleted file mode 100644 index ce2308b3..00000000 --- a/{{ cookiecutter.__package_name_kebab_case }}/tests/test_sentry.py +++ /dev/null @@ -1,11 +0,0 @@ -"""Test Sentry configuration.""" - -import sentry_sdk - -from {{ cookiecutter.__package_name_snake_case }}.sentry import configure_sentry - - -def test_configure_sentry() -> None: - """Test that Sentry can be configured.""" - configure_sentry() - assert sentry_sdk.Hub.current.client is not None diff --git a/{{ cookiecutter.__package_name_kebab_case }}/.devcontainer/devcontainer.json b/{{ cookiecutter.__project_name_kebab_case }}/.devcontainer/devcontainer.json similarity index 95% rename from {{ cookiecutter.__package_name_kebab_case }}/.devcontainer/devcontainer.json rename to {{ cookiecutter.__project_name_kebab_case }}/.devcontainer/devcontainer.json index f9b5df19..f350b7e1 100644 --- a/{{ cookiecutter.__package_name_kebab_case }}/.devcontainer/devcontainer.json +++ b/{{ cookiecutter.__project_name_kebab_case }}/.devcontainer/devcontainer.json @@ -1,5 +1,5 @@ { - "name": "{{ cookiecutter.__package_name_kebab_case }}", + "name": "{{ cookiecutter.__project_name_kebab_case }}", "dockerComposeFile": "../docker-compose.yml", "service": "devcontainer", "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}/", @@ -39,7 +39,7 @@ "jupyter.kernels.excludePythonEnvironments": ["/usr/local/bin/python"], "mypy-type-checker.importStrategy": "fromEnvironment", "notebook.formatOnSave.enabled": true, - "python.defaultInterpreterPath": "/opt/{{ cookiecutter.__package_name_kebab_case }}-env/bin/python", + "python.defaultInterpreterPath": "/opt/{{ cookiecutter.__project_name_kebab_case }}-env/bin/python", "python.terminal.activateEnvironment": false, "python.testing.pytestEnabled": true, "ruff.importStrategy": "fromEnvironment", diff --git a/{{ cookiecutter.__package_name_kebab_case }}/.dockerignore b/{{ cookiecutter.__project_name_kebab_case }}/.dockerignore similarity index 100% rename from {{ cookiecutter.__package_name_kebab_case }}/.dockerignore rename to {{ cookiecutter.__project_name_kebab_case }}/.dockerignore diff --git a/{{ cookiecutter.__package_name_kebab_case }}/.github/dependabot.yml b/{{ cookiecutter.__project_name_kebab_case }}/.github/dependabot.yml similarity index 100% rename from {{ cookiecutter.__package_name_kebab_case }}/.github/dependabot.yml rename to {{ cookiecutter.__project_name_kebab_case }}/.github/dependabot.yml diff --git a/{{ cookiecutter.__package_name_kebab_case }}/.github/workflows/deploy.yml b/{{ cookiecutter.__project_name_kebab_case }}/.github/workflows/deploy.yml similarity index 100% rename from {{ cookiecutter.__package_name_kebab_case }}/.github/workflows/deploy.yml rename to {{ cookiecutter.__project_name_kebab_case }}/.github/workflows/deploy.yml diff --git a/{{ cookiecutter.__package_name_kebab_case }}/.github/workflows/publish.yml b/{{ cookiecutter.__project_name_kebab_case }}/.github/workflows/publish.yml similarity index 100% rename from {{ cookiecutter.__package_name_kebab_case }}/.github/workflows/publish.yml rename to {{ cookiecutter.__project_name_kebab_case }}/.github/workflows/publish.yml diff --git a/{{ cookiecutter.__package_name_kebab_case }}/.github/workflows/test.yml b/{{ cookiecutter.__project_name_kebab_case }}/.github/workflows/test.yml similarity index 90% rename from {{ cookiecutter.__package_name_kebab_case }}/.github/workflows/test.yml rename to {{ cookiecutter.__project_name_kebab_case }}/.github/workflows/test.yml index bc676f1f..48ad286b 100644 --- a/{{ cookiecutter.__package_name_kebab_case }}/.github/workflows/test.yml +++ b/{{ cookiecutter.__project_name_kebab_case }}/.github/workflows/test.yml @@ -28,17 +28,17 @@ jobs: node-version: 21 - name: Install @devcontainers/cli - run: npm install --location=global @devcontainers/cli@0.55.0 + run: npm install --location=global @devcontainers/cli@0.58.0 - name: Start Dev Container run: | git config --global init.defaultBranch main PYTHON_VERSION={% raw %}${{{% endraw %} matrix.python-version }} devcontainer up --workspace-folder . - - name: Lint package + - name: Lint {{ cookiecutter.project_type }} run: devcontainer exec --workspace-folder . poe lint - - name: Test package + - name: Test {{ cookiecutter.project_type }} run: devcontainer exec --workspace-folder . poe test - name: Upload coverage diff --git a/{{ cookiecutter.__package_name_kebab_case }}/.gitignore b/{{ cookiecutter.__project_name_kebab_case }}/.gitignore similarity index 100% rename from {{ cookiecutter.__package_name_kebab_case }}/.gitignore rename to {{ cookiecutter.__project_name_kebab_case }}/.gitignore diff --git a/{{ cookiecutter.__package_name_kebab_case }}/.gitlab-ci.yml b/{{ cookiecutter.__project_name_kebab_case }}/.gitlab-ci.yml similarity index 92% rename from {{ cookiecutter.__package_name_kebab_case }}/.gitlab-ci.yml rename to {{ cookiecutter.__project_name_kebab_case }}/.gitlab-ci.yml index 112a02d4..cebce889 100644 --- a/{{ cookiecutter.__package_name_kebab_case }}/.gitlab-ci.yml +++ b/{{ cookiecutter.__project_name_kebab_case }}/.gitlab-ci.yml @@ -1,7 +1,7 @@ stages: - build - test - - {% if cookiecutter.with_fastapi_api|int or cookiecutter.with_streamlit_app|int %}deploy{% else %}publish{% endif %} + - {% if cookiecutter.project_type == "package" %}publish{% else %}deploy{% endif %} variables: DOCKER_TLS_CERTDIR: "/certs" @@ -18,7 +18,7 @@ variables: - .npm_cache before_script: - mkdir -p .apk_cache && apk add --cache-dir .apk_cache npm - - npm install --cache .npm_cache --global --prefer-offline @devcontainers/cli@0.55.0 + - npm install --cache .npm_cache --global --prefer-offline @devcontainers/cli@0.58.0 # Build the Dev Container. Build: @@ -57,7 +57,7 @@ Build: reports: dotenv: .env -# Lint and test the package. +# Lint and test the {{ cookiecutter.project_type }}. Test: extends: - .python_matrix @@ -69,7 +69,7 @@ Test: script: - | devcontainer up --cache-from "type=registry,ref=$CI_REGISTRY_IMAGE/devcontainer:$PYTHON_VERSION-$CI_IMAGE_SHA" --workspace-folder . - devcontainer exec --workspace-folder . git config --global --add safe.directory /workspaces/{{ cookiecutter.__package_name_kebab_case }} + devcontainer exec --workspace-folder . git config --global --add safe.directory /workspaces/{{ cookiecutter.__project_name_kebab_case }} devcontainer exec --workspace-folder . poe lint devcontainer exec --workspace-folder . poe test coverage: '/^TOTAL.*\s+(\d+(?:\.\d+)?)%/' @@ -84,7 +84,7 @@ Test: untracked: true when: always -{% if not cookiecutter.with_fastapi_api|int and not cookiecutter.with_streamlit_app|int -%} +{% if cookiecutter.project_type == "package" -%} # Publish this package version to {% if cookiecutter.private_package_repository_name %}a private package repository{% else %}PyPI{% endif %}. Publish: stage: publish @@ -101,7 +101,7 @@ Publish: only: - tags {%- else -%} -# Deploy the application to the Docker registry. +# Deploy the app to the Docker registry. Deploy: stage: deploy image: docker:latest diff --git a/{{ cookiecutter.__package_name_kebab_case }}/.pre-commit-config.yaml b/{{ cookiecutter.__project_name_kebab_case }}/.pre-commit-config.yaml similarity index 100% rename from {{ cookiecutter.__package_name_kebab_case }}/.pre-commit-config.yaml rename to {{ cookiecutter.__project_name_kebab_case }}/.pre-commit-config.yaml diff --git a/{{ cookiecutter.__package_name_kebab_case }}/Dockerfile b/{{ cookiecutter.__project_name_kebab_case }}/Dockerfile similarity index 85% rename from {{ cookiecutter.__package_name_kebab_case }}/Dockerfile rename to {{ cookiecutter.__project_name_kebab_case }}/Dockerfile index 4ee75c7b..e3e249aa 100644 --- a/{{ cookiecutter.__package_name_kebab_case }}/Dockerfile +++ b/{{ cookiecutter.__project_name_kebab_case }}/Dockerfile @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1 ARG PYTHON_VERSION={{ cookiecutter.python_version }} -FROM {{ cookiecutter.docker_image }} AS base +FROM {{ cookiecutter.__docker_image }} AS base # Remove docker-clean so we can keep the apt cache in Docker build cache. RUN rm /etc/apt/apt.conf.d/docker-clean @@ -23,12 +23,12 @@ RUN groupadd --gid $GID user && \ USER user # Create and activate a virtual environment. -ENV VIRTUAL_ENV /opt/{{ cookiecutter.__package_name_kebab_case }}-env +ENV VIRTUAL_ENV /opt/{{ cookiecutter.__project_name_kebab_case }}-env ENV PATH $VIRTUAL_ENV/bin:$PATH RUN python -m venv $VIRTUAL_ENV # Set the working directory. -WORKDIR /workspaces/{{ cookiecutter.__package_name_kebab_case }}/ +WORKDIR /workspaces/{{ cookiecutter.__project_name_kebab_case }}/ @@ -53,9 +53,9 @@ RUN --mount=type=cache,target=/var/cache/apt/ \ USER user # Install the run time Python dependencies in the virtual environment. -COPY --chown=user:user poetry.lock* pyproject.toml /workspaces/{{ cookiecutter.__package_name_kebab_case }}/ +COPY --chown=user:user poetry.lock* pyproject.toml /workspaces/{{ cookiecutter.__project_name_kebab_case }}/ RUN mkdir -p /home/user/.cache/pypoetry/ && mkdir -p /home/user/.config/pypoetry/ && \ - mkdir -p src/{{ cookiecutter.__package_name_snake_case }}/ && touch src/{{ cookiecutter.__package_name_snake_case }}/__init__.py && touch README.md + mkdir -p src/{{ cookiecutter.__project_name_snake_case }}/ && touch src/{{ cookiecutter.__project_name_snake_case }}/__init__.py && touch README.md RUN --mount=type=cache,uid=$UID,gid=$GID,target=/home/user/.cache/pypoetry/ \ {%- if cookiecutter.private_package_repository_name %} --mount=type=secret,id=poetry-auth,uid=$UID,gid=$GID,target=/home/user/.config/pypoetry/auth.toml \ @@ -85,7 +85,7 @@ RUN --mount=type=cache,uid=$UID,gid=$GID,target=/home/user/.cache/pypoetry/ \ poetry install --no-interaction # Persist output generated during docker build so that we can restore it in the dev container. -COPY --chown=user:user .pre-commit-config.yaml /workspaces/{{ cookiecutter.__package_name_kebab_case }}/ +COPY --chown=user:user .pre-commit-config.yaml /workspaces/{{ cookiecutter.__project_name_kebab_case }}/ RUN mkdir -p /opt/build/poetry/ && cp poetry.lock /opt/build/poetry/ && \ git init && pre-commit install --install-hooks && \ mkdir -p /opt/build/git/ && cp .git/hooks/commit-msg .git/hooks/pre-commit /opt/build/git/ @@ -111,7 +111,7 @@ RUN git clone --branch v$ANTIDOTE_VERSION --depth=1 https://github.com/mattmc3/a # Enable Poetry to read the private package repository credentials. RUN ln -s /run/secrets/poetry-auth /home/user/.config/pypoetry/auth.toml {%- endif %} -{%- if cookiecutter.with_fastapi_api|int or cookiecutter.with_streamlit_app|int or cookiecutter.with_typer_cli|int %} +{%- if cookiecutter.project_type == "app" %} @@ -120,15 +120,15 @@ FROM base AS app # Copy the virtual environment from the poetry stage. COPY --from=poetry $VIRTUAL_ENV $VIRTUAL_ENV -# Copy the package source code to the working directory. +# Copy the {{ cookiecutter.project_type }} source code to the working directory. COPY --chown=user:user . . -# Expose the application. -{%- if cookiecutter.with_fastapi_api|int or cookiecutter.with_streamlit_app|int %} -ENTRYPOINT ["/opt/{{ cookiecutter.__package_name_kebab_case }}-env/bin/poe"] -CMD [{% if cookiecutter.with_fastapi_api|int %}"api"{% else %}"app"{% endif %}] -{%- else %} -ENTRYPOINT ["/opt/{{ cookiecutter.__package_name_kebab_case }}-env/bin/{{ cookiecutter.__package_name_kebab_case }}"] +# Expose the app. +{%- if cookiecutter.with_typer_cli|int %} +ENTRYPOINT ["/opt/{{ cookiecutter.__project_name_kebab_case }}-env/bin/{{ cookiecutter.__project_name_kebab_case }}"] CMD [] +{%- else %} +ENTRYPOINT ["/opt/{{ cookiecutter.__project_name_kebab_case }}-env/bin/poe"] +CMD [{% if cookiecutter.with_fastapi_api|int %}"api"{% else %}"app"{% endif %}] {%- endif %} {%- endif %} diff --git a/{{ cookiecutter.__package_name_kebab_case }}/README.md b/{{ cookiecutter.__project_name_kebab_case }}/README.md similarity index 80% rename from {{ cookiecutter.__package_name_kebab_case }}/README.md rename to {{ cookiecutter.__project_name_kebab_case }}/README.md index 02398cdc..509a5fa0 100644 --- a/{{ cookiecutter.__package_name_kebab_case }}/README.md +++ b/{{ cookiecutter.__project_name_kebab_case }}/README.md @@ -1,19 +1,53 @@ -[![Open in Dev Containers](https://img.shields.io/static/v1?label=Dev%20Containers&message=Open&color=blue&logo=visualstudiocode)](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url={{ cookiecutter.package_url.replace("https://", "git@").replace(".com/", ".com:") if cookiecutter.private_package_repository_url else cookiecutter.package_url }}) +[![Open in Dev Containers](https://img.shields.io/static/v1?label=Dev%20Containers&message=Open&color=blue&logo=visualstudiocode)](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url={{ cookiecutter.project_url.replace("https://", "git@").replace(".com/", ".com:") if cookiecutter.private_package_repository_url else cookiecutter.project_url }}) -# {{ cookiecutter.package_name }} +# {{ cookiecutter.project_name }} -{{ cookiecutter.package_description }} +{{ cookiecutter.project_description }} +{%- if cookiecutter.project_type == "package" or cookiecutter.with_typer_cli|int %} -## Using +## Installing + +To install this package, run: + +```sh +{% if cookiecutter.private_package_repository_name %}poetry add{% else %}pip install{% endif %} {{ cookiecutter.__project_name_kebab_case }} +``` +{%- endif %} -{% if cookiecutter.with_fastapi_api|int or cookiecutter.with_typer_cli|int or cookiecutter.with_streamlit_app|int %}_Python package_: t{% else %}T{% endif %}o add and install this package as a dependency of your project, run `poetry add {{ cookiecutter.__package_name_kebab_case }}`. +## Using {%- if cookiecutter.with_typer_cli|int %} -_Python CLI_: to view this app's CLI commands once it's installed, run `{{ cookiecutter.__package_name_kebab_case }} --help`. +To view the CLI help information, run: + +```sh +{{ cookiecutter.__project_name_kebab_case }} --help +``` +{%- elif cookiecutter.project_type == "app" -%} + +To serve this app, run: + +```sh +docker compose up app +``` +{%- if cookiecutter.with_fastapi_api|int %} + +and open [localhost:8000](http://localhost:8000) in your browser. {%- endif %} -{%- if cookiecutter.with_fastapi_api|int or cookiecutter.with_streamlit_app|int %} -_Python application_: to serve this {% if cookiecutter.with_fastapi_api|int %}REST API{% else %}Streamlit app{% endif %}, run `docker compose up app` and open [localhost:8000](http://localhost:8000) in your browser. Within the Dev Container, this is equivalent to running {% if cookiecutter.with_fastapi_api|int %}`poe api`{% else %}`poe app`{% endif %}. +Within the Dev Container this is equivalent to: + +```sh +poe {% if cookiecutter.with_fastapi_api|int %}api{% else %}app{% endif %} +``` +{%- else %} + +Example usage: + +```python +import {{ cookiecutter.__project_name_snake_case }} + +... +``` {%- endif %} ## Contributing @@ -25,17 +59,19 @@ _Python application_: to serve this {% if cookiecutter.with_fastapi_api|int %}RE 1. Set up Git to use SSH {% if cookiecutter.continuous_integration == "GitLab" -%} -1. [Generate an SSH key](https://docs.gitlab.com/ee/ssh/README.html#generate-an-ssh-key-pair) and [add the SSH key to your GitLab account](https://docs.gitlab.com/ee/ssh/README.html#add-an-ssh-key-to-your-gitlab-account). +1. [Generate an SSH key](https://docs.gitlab.com/ee/user/ssh.html#generate-an-ssh-key-pair) and [add the SSH key to your GitLab account](https://docs.gitlab.com/ee/user/ssh.html#add-an-ssh-key-to-your-gitlab-account). {%- else -%} 1. [Generate an SSH key](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent#generating-a-new-ssh-key) and [add the SSH key to your GitHub account](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/adding-a-new-ssh-key-to-your-github-account). {%- endif %} 1. Configure SSH to automatically load your SSH keys: ```sh cat << EOF >> ~/.ssh/config + Host * AddKeysToAgent yes IgnoreUnknown UseKeychain UseKeychain yes + ForwardAgent yes EOF ``` @@ -45,11 +81,11 @@ _Python application_: to serve this {% if cookiecutter.with_fastapi_api|int %}RE 2. Install Docker 1. [Install Docker Desktop](https://www.docker.com/get-started). - - Enable _Use Docker Compose V2_ in Docker Desktop's preferences window. - _Linux only_: - Export your user's user id and group id so that [files created in the Dev Container are owned by your user](https://github.com/moby/moby/issues/3206): ```sh cat << EOF >> ~/.bashrc + export UID=$(id --user) export GID=$(id --group) {%- if cookiecutter.private_package_repository_name %} @@ -113,7 +149,7 @@ The following development environments are supported: {% if cookiecutter.continuous_integration == "GitHub" %} 1. ⭐️ _GitHub Codespaces_: click on _Code_ and select _Create codespace_ to start a Dev Container with [GitHub Codespaces](https://github.com/features/codespaces). {%- endif %} -1. ⭐️ _Dev Container (with container volume)_: click on [Open in Dev Containers](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url={{ cookiecutter.package_url.replace("https://", "git@").replace(".com/", ".com:") if cookiecutter.private_package_repository_url else cookiecutter.package_url }}) to clone this repository in a container volume and create a Dev Container with VS Code. +1. ⭐️ _Dev Container (with container volume)_: click on [Open in Dev Containers](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url={{ cookiecutter.project_url.replace("https://", "git@").replace(".com/", ".com:") if cookiecutter.private_package_repository_url else cookiecutter.project_url }}) to clone this repository in a container volume and create a Dev Container with VS Code. 1. _Dev Container_: clone this repository, open it with VS Code, and run Ctrl/⌘ + + P → _Dev Containers: Reopen in Container_. 1. _PyCharm_: clone this repository, open it with PyCharm, and [configure Docker Compose as a remote interpreter](https://www.jetbrains.com/help/pycharm/using-docker-compose-as-a-remote-interpreter.html#docker-compose-remote) with the `dev` service. 1. _Terminal_: clone this repository, open it with your terminal, and run `docker compose up --detach dev` to start a Dev Container in the background, and then run `docker compose exec dev zsh` to open a shell prompt in the Dev Container. @@ -129,7 +165,7 @@ The following development environments are supported: - Run `poetry add {package}` from within the development environment to install a run time dependency and add it to `pyproject.toml` and `poetry.lock`. Add `--group test` or `--group dev` to install a CI or development dependency, respectively. - Run `poetry update` from within the development environment to upgrade all dependencies to the latest versions allowed by `pyproject.toml`. {%- if cookiecutter.with_conventional_commits|int %} -- Run `cz bump` to bump the package's version, update the `CHANGELOG.md`, and create a git tag. +- Run `cz bump` to bump the {{ cookiecutter.project_type }}'s version, update the `CHANGELOG.md`, and create a git tag. {%- endif %} diff --git a/{{ cookiecutter.__package_name_kebab_case }}/docker-compose.yml b/{{ cookiecutter.__project_name_kebab_case }}/docker-compose.yml similarity index 76% rename from {{ cookiecutter.__package_name_kebab_case }}/docker-compose.yml rename to {{ cookiecutter.__project_name_kebab_case }}/docker-compose.yml index 9901e056..07c1e58d 100644 --- a/{{ cookiecutter.__package_name_kebab_case }}/docker-compose.yml +++ b/{{ cookiecutter.__project_name_kebab_case }}/docker-compose.yml @@ -34,14 +34,14 @@ services: [ "sh", "-c", - "sudo chown user $$SSH_AUTH_SOCK && cp --update /opt/build/poetry/poetry.lock /workspaces/{{ cookiecutter.__package_name_kebab_case }}/ && mkdir -p /workspaces/{{ cookiecutter.__package_name_kebab_case }}/.git/hooks/ && cp --update /opt/build/git/* /workspaces/{{ cookiecutter.__package_name_kebab_case }}/.git/hooks/ && zsh" + "sudo chown user $$SSH_AUTH_SOCK && cp --update /opt/build/poetry/poetry.lock /workspaces/{{ cookiecutter.__project_name_kebab_case }}/ && mkdir -p /workspaces/{{ cookiecutter.__project_name_kebab_case }}/.git/hooks/ && cp --update /opt/build/git/* /workspaces/{{ cookiecutter.__project_name_kebab_case }}/.git/hooks/ && zsh" ] environment: {%- if not cookiecutter.private_package_repository_name %} - POETRY_PYPI_TOKEN_PYPI {%- endif %} - SSH_AUTH_SOCK=/run/host-services/ssh-auth.sock - {%- if cookiecutter.with_fastapi_api|int or cookiecutter.with_streamlit_app|int %} + {%- if cookiecutter.with_fastapi_api|int %} ports: - "8000" {%- endif %} @@ -51,7 +51,7 @@ services: - ${SSH_AGENT_AUTH_SOCK:-/run/host-services/ssh-auth.sock}:/run/host-services/ssh-auth.sock profiles: - dev - {%- if cookiecutter.with_fastapi_api|int or cookiecutter.with_streamlit_app|int or cookiecutter.with_typer_cli|int %} + {%- if cookiecutter.project_type == "app" %} app: build: @@ -62,7 +62,7 @@ services: - poetry-auth {%- endif %} tty: true - {%- if cookiecutter.with_fastapi_api|int or cookiecutter.with_streamlit_app|int %} + {%- if cookiecutter.with_fastapi_api|int %} ports: - "8000:8000" {%- endif %} diff --git a/{{ cookiecutter.__package_name_kebab_case }}/pyproject.toml b/{{ cookiecutter.__project_name_kebab_case }}/pyproject.toml similarity index 71% rename from {{ cookiecutter.__package_name_kebab_case }}/pyproject.toml rename to {{ cookiecutter.__project_name_kebab_case }}/pyproject.toml index 6b76acc5..2e6644ef 100644 --- a/{{ cookiecutter.__package_name_kebab_case }}/pyproject.toml +++ b/{{ cookiecutter.__project_name_kebab_case }}/pyproject.toml @@ -3,12 +3,12 @@ requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" [tool.poetry] # https://python-poetry.org/docs/pyproject/ -name = "{{ cookiecutter.__package_name_kebab_case }}" +name = "{{ cookiecutter.__project_name_kebab_case }}" version = "0.0.0" -description = "{{ cookiecutter.package_description }}" +description = "{{ cookiecutter.project_description }}" authors = ["{{ cookiecutter.author_name }} <{{ cookiecutter.author_email }}>"] readme = "README.md" -repository = "{{ cookiecutter.package_url }}" +repository = "{{ cookiecutter.project_url }}" {%- if cookiecutter.with_conventional_commits|int %} [tool.commitizen] # https://commitizen-tools.github.io/commitizen/config/ @@ -20,60 +20,50 @@ version_provider = "poetry" {%- if cookiecutter.with_typer_cli|int %} [tool.poetry.scripts] # https://python-poetry.org/docs/pyproject/#scripts -{{ cookiecutter.__package_name_kebab_case }} = "{{ cookiecutter.__package_name_snake_case }}.cli:app" +{{ cookiecutter.__project_name_kebab_case }} = "{{ cookiecutter.__project_name_snake_case }}.cli:app" {%- endif %} [tool.poetry.dependencies] # https://python-poetry.org/docs/dependency-specification/ {%- if cookiecutter.with_fastapi_api|int %} -coloredlogs = ">=15.0.1" -fastapi = { extras = ["all"], version = ">=0.92.0" } -gunicorn = ">=20.1.0" +coloredlogs = "^15.0.1" +fastapi = { extras = ["all"], version = "^0.110.1" } +gunicorn = "^21.2.0" {%- endif %} -{%- if cookiecutter.with_fastapi_api|int or cookiecutter.with_streamlit_app|int %} -poethepoet = ">=0.20.0" -{%- endif %} -{%- if cookiecutter.with_pydantic_typing|int %} -pydantic = ">=1.10.7" +{%- if cookiecutter.project_type == "app" %} +poethepoet = "^0.25.0" {%- endif %} python = ">={{ cookiecutter.python_version }},<4.0" -{%- if cookiecutter.with_sentry_logging|int %} -sentry-sdk = ">=1.16.0" -{%- endif %} -{%- if cookiecutter.with_streamlit_app|int %} -streamlit = ">=1.19.0" -{%- endif %} {%- if cookiecutter.with_typer_cli|int %} -typer = { extras = ["all"], version = ">=0.9.0" } +typer = { extras = ["all"], version = "^0.12.0" } {%- endif %} {%- if cookiecutter.with_fastapi_api|int %} -uvicorn = { extras = ["standard"], version = ">=0.20.0" } +uvicorn = { extras = ["standard"], version = "^0.29.0" } {%- endif %} [tool.poetry.group.test.dependencies] # https://python-poetry.org/docs/master/managing-dependencies/ {%- if cookiecutter.with_conventional_commits|int %} -commitizen = ">=3.2.1" +commitizen = ">=3.21.3" {%- endif %} -coverage = { extras = ["toml"], version = ">=7.2.5" } -mypy = ">=1.2.0" -{%- if not cookiecutter.with_fastapi_api|int and not cookiecutter.with_streamlit_app|int %} -poethepoet = ">=0.20.0" +coverage = { extras = ["toml"], version = ">=7.4.4" } +mypy = ">=1.9.0" +{%- if cookiecutter.project_type == "package" %} +poethepoet = ">=0.25.0" {%- endif %} -pre-commit = ">=3.3.1" -pytest = ">=7.3.1" -pytest-clarity = ">=1.0.1" -pytest-mock = ">=3.10.0" -pytest-xdist = ">=3.2.1" -ruff = ">=0.2.1" +pre-commit = ">=3.7.0" +pytest = ">=8.1.1" +pytest-mock = ">=3.14.0" +pytest-xdist = ">=3.5.0" +ruff = ">=0.3.5" {%- if cookiecutter.development_environment == "strict" %} -safety = ">=2.3.4,!=2.3.5" -shellcheck-py = ">=0.9.0" -typeguard = ">=3.0.2" +safety = ">=3.1.0" +shellcheck-py = ">=0.10.0.1" +typeguard = ">=4.2.1" {%- endif %} [tool.poetry.group.dev.dependencies] # https://python-poetry.org/docs/master/managing-dependencies/ -cruft = ">=2.14.0" -ipykernel = ">=6.29.2" -pdoc = ">=13.1.1" +cruft = ">=2.15.0" +ipykernel = ">=6.29.4" +pdoc = ">=14.4.0" {%- if cookiecutter.private_package_repository_name %} [[tool.poetry.source]] @@ -105,7 +95,7 @@ output = "reports/coverage.xml" [tool.mypy] # https://mypy.readthedocs.io/en/latest/config_file.html junit_xml = "reports/mypy.xml" -{%- if cookiecutter.with_fastapi_api|int or cookiecutter.with_pydantic_typing|int %} +{%- if cookiecutter.with_fastapi_api|int %} plugins = "pydantic.mypy" {%- endif %} {%- if cookiecutter.development_environment == "strict" %} @@ -119,7 +109,7 @@ show_column_numbers = true show_error_codes = true show_error_context = true warn_unreachable = true -{%- if cookiecutter.development_environment == "strict" and (cookiecutter.with_fastapi_api|int or cookiecutter.with_pydantic_typing|int) %} +{%- if cookiecutter.development_environment == "strict" and cookiecutter.with_fastapi_api|int %} [tool.pydantic-mypy] # https://pydantic-docs.helpmanual.io/mypy_plugin/#configuring-the-plugin init_forbid_extra = true @@ -129,7 +119,7 @@ warn_untyped_fields = true {%- endif %} [tool.pytest.ini_options] # https://docs.pytest.org/en/latest/reference/reference.html#ini-options-ref -addopts = "--color=yes --doctest-modules --exitfirst --failed-first{% if cookiecutter.development_environment == 'strict' %} --strict-config --strict-markers --typeguard-packages={{ cookiecutter.__package_name_snake_case }}{% endif %} --verbosity=2 --junitxml=reports/pytest.xml" +addopts = "--color=yes --doctest-modules --exitfirst --failed-first{% if cookiecutter.development_environment == 'strict' %} --strict-config --strict-markers --typeguard-packages={{ cookiecutter.__project_name_snake_case }}{% endif %} --verbosity=2 --junitxml=reports/pytest.xml" {%- if cookiecutter.development_environment == "strict" %} filterwarnings = ["error", "ignore::DeprecationWarning"] {%- endif %} @@ -163,13 +153,13 @@ max-doc-length = 100 {%- endif %} [tool.ruff.lint.pydocstyle] -convention = "{{ cookiecutter.docstring_style|lower }}" +convention = "{{ cookiecutter.__docstring_style|lower }}" [tool.poe.tasks] # https://github.com/nat-n/poethepoet {%- if cookiecutter.with_fastapi_api|int %} [tool.poe.tasks.api] - help = "Serve a REST API" + help = "Serve the REST API" shell = """ if [ $dev ] then { @@ -177,7 +167,7 @@ convention = "{{ cookiecutter.docstring_style|lower }}" --host $host \ --port $port \ --reload \ - {{ cookiecutter.__package_name_snake_case }}.api:app + {{ cookiecutter.__project_name_snake_case }}.api:app } else { gunicorn \ --access-logfile - \ @@ -189,7 +179,7 @@ convention = "{{ cookiecutter.docstring_style|lower }}" --worker-class uvicorn.workers.UvicornWorker \ --worker-tmp-dir /dev/shm \ --workers 2 \ - {{ cookiecutter.__package_name_snake_case }}.api:app + {{ cookiecutter.__project_name_snake_case }}.api:app } fi """ @@ -210,48 +200,29 @@ convention = "{{ cookiecutter.docstring_style|lower }}" type = "boolean" name = "dev" options = ["--dev"] -{%- endif %} -{%- if cookiecutter.with_streamlit_app|int %} +{%- elif cookiecutter.project_type == "app" %} [tool.poe.tasks.app] - help = "Serve a Streamlit app" - cmd = """ - streamlit run - --browser.gatherUsageStats false - --server.address $host - --server.port $port - --theme.base light - src/{{ cookiecutter.__package_name_snake_case }}/app.py - """ - use_exec = true + help = "Serve the app" - [[tool.poe.tasks.app.args]] - help = "Bind socket to this host (default: 0.0.0.0)" - name = "host" - options = ["--host"] - default = "0.0.0.0" - - [[tool.poe.tasks.app.args]] - help = "Bind socket to this port (default: 8000)" - name = "port" - options = ["--port"] - default = "8000" + [[tool.poe.tasks.lint.sequence]] + cmd = "echo 'Serving app...'" {%- endif %} [tool.poe.tasks.docs] - help = "Generate this package's docs" + help = "Generate this {{ cookiecutter.project_type }}'s docs" cmd = """ pdoc --docformat $docformat --output-directory $outputdirectory - {{ cookiecutter.__package_name_snake_case }} + {{ cookiecutter.__project_name_snake_case }} """ [[tool.poe.tasks.docs.args]] - help = "The docstring style (default: {{ cookiecutter.docstring_style|lower }})" + help = "The docstring style (default: {{ cookiecutter.__docstring_style|lower }})" name = "docformat" options = ["--docformat"] - default = "{{ cookiecutter.docstring_style|lower }}" + default = "{{ cookiecutter.__docstring_style|lower }}" [[tool.poe.tasks.docs.args]] help = "The output directory (default: docs)" @@ -260,7 +231,7 @@ convention = "{{ cookiecutter.docstring_style|lower }}" default = "docs" [tool.poe.tasks.lint] - help = "Lint this package" + help = "Lint this {{ cookiecutter.project_type }}" [[tool.poe.tasks.lint.sequence]] cmd = """ @@ -275,7 +246,7 @@ convention = "{{ cookiecutter.docstring_style|lower }}" {%- endif %} [tool.poe.tasks.test] - help = "Test this package" + help = "Test this {{ cookiecutter.project_type }}" [[tool.poe.tasks.test.sequence]] cmd = "coverage run" diff --git a/{{ cookiecutter.__project_name_kebab_case }}/src/{{ cookiecutter.__project_name_snake_case }}/__init__.py b/{{ cookiecutter.__project_name_kebab_case }}/src/{{ cookiecutter.__project_name_snake_case }}/__init__.py new file mode 100644 index 00000000..092b9828 --- /dev/null +++ b/{{ cookiecutter.__project_name_kebab_case }}/src/{{ cookiecutter.__project_name_snake_case }}/__init__.py @@ -0,0 +1 @@ +"""{{ cookiecutter.project_name }}.""" diff --git a/{{ cookiecutter.__project_name_kebab_case }}/src/{{ cookiecutter.__project_name_snake_case }}/api.py b/{{ cookiecutter.__project_name_kebab_case }}/src/{{ cookiecutter.__project_name_snake_case }}/api.py new file mode 100644 index 00000000..02bd6a94 --- /dev/null +++ b/{{ cookiecutter.__project_name_kebab_case }}/src/{{ cookiecutter.__project_name_snake_case }}/api.py @@ -0,0 +1,36 @@ +"""{{ cookiecutter.project_name }} REST API.""" + +import asyncio +import logging +from collections.abc import AsyncGenerator +from contextlib import asynccontextmanager + +import coloredlogs +from fastapi import FastAPI + + +@asynccontextmanager +async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]: + """Handle FastAPI startup and shutdown events.""" + # Startup events: + # - Remove all handlers associated with the root logger object. + for handler in logging.root.handlers: + logging.root.removeHandler(handler) + # - Add coloredlogs' colored StreamHandler to the root logger. + coloredlogs.install() + yield + # Shutdown events. + + +app = FastAPI(lifespan=lifespan) + + +@app.get("/compute") +async def compute(n: int = 42) -> int: + """Compute the result of a CPU-bound function.""" + + def fibonacci(n: int) -> int: + return n if n <= 1 else fibonacci(n - 1) + fibonacci(n - 2) + + result = await asyncio.to_thread(fibonacci, n) + return result diff --git a/{{ cookiecutter.__project_name_kebab_case }}/src/{{ cookiecutter.__project_name_snake_case }}/cli.py b/{{ cookiecutter.__project_name_kebab_case }}/src/{{ cookiecutter.__project_name_snake_case }}/cli.py new file mode 100644 index 00000000..142b1860 --- /dev/null +++ b/{{ cookiecutter.__project_name_kebab_case }}/src/{{ cookiecutter.__project_name_snake_case }}/cli.py @@ -0,0 +1,12 @@ +"""{{ cookiecutter.project_name }} CLI.""" + +import typer +from rich import print + +app = typer.Typer() + + +@app.command() +def fire(name: str = "Chell") -> None: + """Fire portal gun.""" + print(f"[bold red]Alert![/bold red] {name} fired [green]portal gun[/green] :boom:") diff --git a/{{ cookiecutter.__package_name_kebab_case }}/src/{{ cookiecutter.__package_name_snake_case }}/py.typed b/{{ cookiecutter.__project_name_kebab_case }}/src/{{ cookiecutter.__project_name_snake_case }}/py.typed similarity index 100% rename from {{ cookiecutter.__package_name_kebab_case }}/src/{{ cookiecutter.__package_name_snake_case }}/py.typed rename to {{ cookiecutter.__project_name_kebab_case }}/src/{{ cookiecutter.__project_name_snake_case }}/py.typed diff --git a/{{ cookiecutter.__project_name_kebab_case }}/tests/__init__.py b/{{ cookiecutter.__project_name_kebab_case }}/tests/__init__.py new file mode 100644 index 00000000..2c168973 --- /dev/null +++ b/{{ cookiecutter.__project_name_kebab_case }}/tests/__init__.py @@ -0,0 +1 @@ +"""{{ cookiecutter.project_name }} test suite.""" diff --git a/{{ cookiecutter.__package_name_kebab_case }}/tests/test_api.py b/{{ cookiecutter.__project_name_kebab_case }}/tests/test_api.py similarity index 56% rename from {{ cookiecutter.__package_name_kebab_case }}/tests/test_api.py rename to {{ cookiecutter.__project_name_kebab_case }}/tests/test_api.py index 0c9fcfa9..34d0b5a9 100644 --- a/{{ cookiecutter.__package_name_kebab_case }}/tests/test_api.py +++ b/{{ cookiecutter.__project_name_kebab_case }}/tests/test_api.py @@ -1,14 +1,14 @@ -"""Test {{ cookiecutter.package_name }} REST API.""" +"""Test {{ cookiecutter.project_name }} REST API.""" import httpx from fastapi.testclient import TestClient -from {{ cookiecutter.__package_name_snake_case }}.api import app +from {{ cookiecutter.__project_name_snake_case }}.api import app client = TestClient(app) def test_read_root() -> None: """Test that reading the root is successful.""" - response = client.get("/") + response = client.get("/compute", params={"n": 7}) assert httpx.codes.is_success(response.status_code) diff --git a/{{ cookiecutter.__project_name_kebab_case }}/tests/test_cli.py b/{{ cookiecutter.__project_name_kebab_case }}/tests/test_cli.py new file mode 100644 index 00000000..7ff89f4f --- /dev/null +++ b/{{ cookiecutter.__project_name_kebab_case }}/tests/test_cli.py @@ -0,0 +1,15 @@ +"""Test {{ cookiecutter.project_name }} CLI.""" + +from typer.testing import CliRunner + +from {{ cookiecutter.__project_name_snake_case }}.cli import app + +runner = CliRunner() + + +def test_fire() -> None: + """Test that the fire command works as expected.""" + name = "GLaDOS" + result = runner.invoke(app, ["--name", name]) + assert result.exit_code == 0 + assert name in result.stdout diff --git a/{{ cookiecutter.__project_name_kebab_case }}/tests/test_import.py b/{{ cookiecutter.__project_name_kebab_case }}/tests/test_import.py new file mode 100644 index 00000000..9cdcbcc0 --- /dev/null +++ b/{{ cookiecutter.__project_name_kebab_case }}/tests/test_import.py @@ -0,0 +1,8 @@ +"""Test {{ cookiecutter.project_name }}.""" + +import {{ cookiecutter.__project_name_snake_case }} + + +def test_import() -> None: + """Test that the {{ cookiecutter.project_type }} can be imported.""" + assert isinstance({{ cookiecutter.__project_name_snake_case }}.__name__, str) From 7d5ffdc454f16c10222b8360316052944f05dc78 Mon Sep 17 00:00:00 2001 From: Laurent Sorber Date: Wed, 17 Apr 2024 16:40:56 +0200 Subject: [PATCH 02/17] docs: improve README (#230) --- .devcontainer/devcontainer.json | 1 + README.md | 45 ++++++++++++++++++--------------- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index cbca8927..b02b1658 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -6,6 +6,7 @@ "customizations": { "vscode": { "extensions": [ + "DavidAnson.vscode-markdownlint", "ms-python.python", "tamasfe.even-better-toml", "visualstudioexptteam.vscodeintellicode" diff --git a/README.md b/README.md index 9406228b..9831df29 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,12 @@ [![Open in Dev Containers](https://img.shields.io/static/v1?label=Dev%20Containers&message=Open&color=blue&logo=visualstudiocode)](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/radix-ai/poetry-cookiecutter) [![Open in GitHub Codespaces](https://img.shields.io/static/v1?label=GitHub%20Codespaces&message=Open&color=blue&logo=github)](https://github.com/codespaces/new?hide_repo_select=true&ref=main&repo=444870763) -# Poetry Cookiecutter +# 🍪 Poetry Cookiecutter A modern [Cookiecutter](https://github.com/cookiecutter/cookiecutter) template for scaffolding Python packages and apps. ## 🍿 Demo -See [Conformal Tights](https://github.com/radix-ai/conformal-tights) for an example of a Python package that is scaffolded with this template. - -Starting development can be done with a single click by [opening the package in GitHub Codespaces](https://github.com/codespaces/new?hide_repo_select=true&ref=main&repo=765698489&skip_quickstart=true), or [opening the package in a Dev Container](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/radix-ai/conformal-tights). +See 👖 [Conformal Tights](https://github.com/radix-ai/conformal-tights) for an example of a Python package that is scaffolded with this template. Contributing to this package can be done with a single click by [starting a GitHub Codespace](https://github.com/codespaces/new?hide_repo_select=true&ref=main&repo=765698489&skip_quickstart=true) or [starting a Dev Container](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/radix-ai/conformal-tights). ## 🎁 Features @@ -34,15 +32,20 @@ Starting development can be done with a single click by [opening the package in To create a new Python project with this template: 1. Install the latest [Cruft](https://github.com/cruft/cruft) and [Cookiecutter](https://github.com/cookiecutter/cookiecutter) in your [Python environment](https://github.com/pyenv/pyenv-virtualenv) with: + ```sh pip install --upgrade "cruft>=2.12.0" "cookiecutter>=2.1.1" ``` -2. Create a new repository for your Python project, then clone it locally. + +2. [Create a new repository](https://github.com/new) for your Python project, then clone it locally. 3. Run the following command in the parent directory of the cloned repository to apply the Poetry Cookiecutter template: + ```sh cruft create -f https://github.com/radix-ai/poetry-cookiecutter ``` + 4. _Optional:_ if your repository name differs from your project's slugified name (see `project_name` in the [Template parameters](https://github.com/radix-ai/poetry-cookiecutter#-template-parameters) below), you will need to copy the scaffolded project into the repository with: + ```sh cp -r {project-name}/ {repository-name}/ ``` @@ -56,19 +59,19 @@ To update your Python project with the latest template: ## 🤓 Template parameters -| Parameter | Description | -| ----------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `project_type`
["package", "app"] | Whether the project is a publishable Python package or a deployable Python app. | -| `project_name`
"Spline Reticulator" | The name of the project. Will be slugified to `snake_case` for importing and `kebab-case` for installing. For example, `My Package` will be `my_package` for importing and `my-package` for installing. | -| `project_description`
"A Python package that reticulates splines." | A single-line description of the project. | -| `project_url`
"https://github.com/user/spline-reticulator" | The URL to the project's repository. | -| `author_name`
"John Smith" | The full name of the primary author of the project. | -| `author_email`
"john@example.com" | The email address of the primary author of the project. | -| `python_version`
"3.10" | The minimum Python version that the project requires. | -| `development_environment`
["simple", "strict"] | Whether to configure the development environment with a focus on simplicity or with a focus on strictness. In strict mode, additional [Ruff rules](https://beta.ruff.rs/docs/rules/) are added, and tools such as [Mypy](https://github.com/python/mypy) and [Pytest](https://github.com/pytest-dev/pytest) are set to strict mode. | -| `with_conventional_commits`
["0", "1"] | If "1", [Commitizen](https://github.com/commitizen-tools/commitizen) will verify that your commits follow the [Conventional Commits](https://www.conventionalcommits.org/) standard. In return, `cz bump` may be used to automate [Semantic Versioning](https://semver.org/) and [Keep A Changelog](https://keepachangelog.com/). | -| `with_fastapi_api`
["0", "1"] | If "1", [FastAPI](https://github.com/tiangolo/fastapi) is added as a run time dependency, FastAPI API stubs and tests are added, a `poe api` command for serving the API is added. | -| `with_typer_cli`
["0", "1"] | If "1", [Typer](https://github.com/tiangolo/typer) is added as a run time dependency, Typer CLI stubs and tests are added, the package itself is registered as a CLI. | -| `continuous_integration`
["GitHub", "GitLab"] | Whether to include a [GitHub Actions](https://docs.github.com/en/actions) or a [GitLab CI/CD](https://docs.gitlab.com/ee/ci/) continuous integration workflow for testing the project, and publishing the package or deploying the app. | -| `private_package_repository_name`
"Private Package Repository" | Optional name of a private package repository to install packages from and publish this package to. | -| `private_package_repository_url`
"https://pypi.example.com/simple" | Optional URL of a private package repository to install packages from and publish this package to. Make sure to include the `/simple` suffix. For instance, when using a GitLab Package Registry this value should be of the form `https://gitlab.com/api/v4/projects/` `{project_id}` `/packages/pypi/simple`. | +| Parameter | Description | +| ------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `project_type`
["package", "app"] | Whether the project is a publishable Python package or a deployable Python app. | +| `project_name`
"Spline Reticulator" | The name of the project. Will be slugified to `snake_case` for importing and `kebab-case` for installing. For example, `My Package` will be `my_package` for importing and `my-package` for installing. | +| `project_description`
"A Python package that reticulates splines." | A single-line description of the project. | +| `project_url`
"" | The URL to the project's repository. | +| `author_name`
"John Smith" | The full name of the primary author of the project. | +| `author_email`
"" | The email address of the primary author of the project. | +| `python_version`
"3.10" | The minimum Python version that the project requires. | +| `development_environment`
["simple", "strict"] | Whether to configure the development environment with a focus on simplicity or with a focus on strictness. In strict mode, additional [Ruff rules](https://beta.ruff.rs/docs/rules/) are added, and tools such as [Mypy](https://github.com/python/mypy) and [Pytest](https://github.com/pytest-dev/pytest) are set to strict mode. | +| `with_conventional_commits`
["0", "1"] | If "1", [Commitizen](https://github.com/commitizen-tools/commitizen) will verify that your commits follow the [Conventional Commits](https://www.conventionalcommits.org/) standard. In return, `cz bump` may be used to automate [Semantic Versioning](https://semver.org/) and [Keep A Changelog](https://keepachangelog.com/). | +| `with_fastapi_api`
["0", "1"] | If "1", [FastAPI](https://github.com/tiangolo/fastapi) is added as a run time dependency, FastAPI API stubs and tests are added, a `poe api` command for serving the API is added. | +| `with_typer_cli`
["0", "1"] | If "1", [Typer](https://github.com/tiangolo/typer) is added as a run time dependency, Typer CLI stubs and tests are added, the package itself is registered as a CLI. | +| `continuous_integration`
["GitHub", "GitLab"] | Whether to include a [GitHub Actions](https://docs.github.com/en/actions) or a [GitLab CI/CD](https://docs.gitlab.com/ee/ci/) continuous integration workflow for testing the project, and publishing the package or deploying the app. | +| `private_package_repository_name`
"Private Package Repository" | Optional name of a private package repository to install packages from and publish this package to. | +| `private_package_repository_url`
"" | Optional URL of a private package repository to install packages from and publish this package to. Make sure to include the `/simple` suffix. For instance, when using a GitLab Package Registry this value should be of the form `https://gitlab.com/api/v4/projects/` `{project_id}` `/packages/pypi/simple`. | From 700e0a83a286c726266161c56b9d597fd0503070 Mon Sep 17 00:00:00 2001 From: Laurent Sorber Date: Fri, 19 Apr 2024 15:18:19 +0200 Subject: [PATCH 03/17] feat: add official GitHub/GitLab VS Code extension (#223) feat: add GitHub/GitLab VS Code extension --- .../.devcontainer/devcontainer.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/{{ cookiecutter.__project_name_kebab_case }}/.devcontainer/devcontainer.json b/{{ cookiecutter.__project_name_kebab_case }}/.devcontainer/devcontainer.json index f350b7e1..fb978056 100644 --- a/{{ cookiecutter.__project_name_kebab_case }}/.devcontainer/devcontainer.json +++ b/{{ cookiecutter.__project_name_kebab_case }}/.devcontainer/devcontainer.json @@ -10,6 +10,12 @@ "vscode": { "extensions": [ "charliermarsh.ruff", + {%- if cookiecutter.continuous_integration == "GitHub" %} + "GitHub.vscode-github-actions", + "GitHub.vscode-pull-request-github", + {%- elif cookiecutter.continuous_integration == "GitLab" %} + "GitLab.gitlab-workflow", + {%- endif %} "ms-python.mypy-type-checker", "ms-python.python", "ms-toolsai.jupyter", From abaeed12c873f428adf44484629cc74da46ec07b Mon Sep 17 00:00:00 2001 From: Laurent Sorber Date: Fri, 19 Apr 2024 15:18:32 +0200 Subject: [PATCH 04/17] fix: install extras (#227) --- {{ cookiecutter.__project_name_kebab_case }}/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/{{ cookiecutter.__project_name_kebab_case }}/Dockerfile b/{{ cookiecutter.__project_name_kebab_case }}/Dockerfile index e3e249aa..3bfb51a8 100644 --- a/{{ cookiecutter.__project_name_kebab_case }}/Dockerfile +++ b/{{ cookiecutter.__project_name_kebab_case }}/Dockerfile @@ -60,7 +60,7 @@ RUN --mount=type=cache,uid=$UID,gid=$GID,target=/home/user/.cache/pypoetry/ \ {%- if cookiecutter.private_package_repository_name %} --mount=type=secret,id=poetry-auth,uid=$UID,gid=$GID,target=/home/user/.config/pypoetry/auth.toml \ {%- endif %} - poetry install --only main --no-interaction + poetry install --only main --all-extras --no-interaction @@ -82,7 +82,7 @@ RUN --mount=type=cache,uid=$UID,gid=$GID,target=/home/user/.cache/pypoetry/ \ {%- if cookiecutter.private_package_repository_name %} --mount=type=secret,id=poetry-auth,uid=$UID,gid=$GID,target=/home/user/.config/pypoetry/auth.toml \ {%- endif %} - poetry install --no-interaction + poetry install --all-extras --no-interaction # Persist output generated during docker build so that we can restore it in the dev container. COPY --chown=user:user .pre-commit-config.yaml /workspaces/{{ cookiecutter.__project_name_kebab_case }}/ From e860ec04b580b24fbb6ee50671cddbf508e42a27 Mon Sep 17 00:00:00 2001 From: Laurent Sorber Date: Fri, 19 Apr 2024 15:18:43 +0200 Subject: [PATCH 05/17] feat: add ipywidgets to dev dependencies (#229) --- {{ cookiecutter.__project_name_kebab_case }}/pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/{{ cookiecutter.__project_name_kebab_case }}/pyproject.toml b/{{ cookiecutter.__project_name_kebab_case }}/pyproject.toml index 2e6644ef..874507cf 100644 --- a/{{ cookiecutter.__project_name_kebab_case }}/pyproject.toml +++ b/{{ cookiecutter.__project_name_kebab_case }}/pyproject.toml @@ -63,6 +63,7 @@ typeguard = ">=4.2.1" [tool.poetry.group.dev.dependencies] # https://python-poetry.org/docs/master/managing-dependencies/ cruft = ">=2.15.0" ipykernel = ">=6.29.4" +ipywidgets = ">=8.1.2" pdoc = ">=14.4.0" {%- if cookiecutter.private_package_repository_name %} From 27b6ceb652a41df9b7bdb91e2664daa445d1ac40 Mon Sep 17 00:00:00 2001 From: Laurent Sorber Date: Fri, 19 Apr 2024 15:18:53 +0200 Subject: [PATCH 06/17] fix: loosen runtime dependency specification (#231) --- .../pyproject.toml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/{{ cookiecutter.__project_name_kebab_case }}/pyproject.toml b/{{ cookiecutter.__project_name_kebab_case }}/pyproject.toml index 874507cf..94775a95 100644 --- a/{{ cookiecutter.__project_name_kebab_case }}/pyproject.toml +++ b/{{ cookiecutter.__project_name_kebab_case }}/pyproject.toml @@ -25,19 +25,19 @@ version_provider = "poetry" [tool.poetry.dependencies] # https://python-poetry.org/docs/dependency-specification/ {%- if cookiecutter.with_fastapi_api|int %} -coloredlogs = "^15.0.1" -fastapi = { extras = ["all"], version = "^0.110.1" } -gunicorn = "^21.2.0" +coloredlogs = ">=15.0.1" +fastapi = { extras = ["all"], version = ">=0.110.1" } +gunicorn = ">=21.2.0" {%- endif %} {%- if cookiecutter.project_type == "app" %} -poethepoet = "^0.25.0" +poethepoet = ">=0.25.0" {%- endif %} python = ">={{ cookiecutter.python_version }},<4.0" {%- if cookiecutter.with_typer_cli|int %} -typer = { extras = ["all"], version = "^0.12.0" } +typer = { extras = ["all"], version = ">=0.12.0" } {%- endif %} {%- if cookiecutter.with_fastapi_api|int %} -uvicorn = { extras = ["standard"], version = "^0.29.0" } +uvicorn = { extras = ["standard"], version = ">=0.29.0" } {%- endif %} [tool.poetry.group.test.dependencies] # https://python-poetry.org/docs/master/managing-dependencies/ From 8ab7ee0accff3b9cb0e470bfc925858fc04d1e97 Mon Sep 17 00:00:00 2001 From: Laurent Sorber Date: Sun, 21 Apr 2024 12:06:03 +0200 Subject: [PATCH 07/17] feat: optimize Dependabot config (#220) --- .github/workflows/test.yml | 2 +- .../.github/dependabot.yml | 24 +++++++++++++++---- .../.github/workflows/publish.yml | 2 +- .../.github/workflows/test.yml | 2 +- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0188a3be..5f19accb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,7 +25,7 @@ jobs: path: template - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.9" diff --git a/{{ cookiecutter.__project_name_kebab_case }}/.github/dependabot.yml b/{{ cookiecutter.__project_name_kebab_case }}/.github/dependabot.yml index e98ee6ed..b402007e 100644 --- a/{{ cookiecutter.__project_name_kebab_case }}/.github/dependabot.yml +++ b/{{ cookiecutter.__project_name_kebab_case }}/.github/dependabot.yml @@ -8,15 +8,29 @@ updates: commit-message: prefix: "ci" prefix-development: "ci" - include: "scope" + include: scope + groups: + ci-dependencies: + patterns: + - "*" - package-ecosystem: pip directory: / schedule: interval: monthly commit-message: - prefix: "build" + prefix: "chore" prefix-development: "build" - include: "scope" - versioning-strategy: lockfile-only + include: scope allow: - - dependency-type: "all" + {%- if cookiecutter.project_type == "app" %} + - dependency-type: production + {%- endif %} + - dependency-type: development + versioning-strategy: increase + groups: + {%- if cookiecutter.project_type == "app" %} + runtime-dependencies: + dependency-type: production + {%- endif %} + development-dependencies: + dependency-type: development diff --git a/{{ cookiecutter.__project_name_kebab_case }}/.github/workflows/publish.yml b/{{ cookiecutter.__project_name_kebab_case }}/.github/workflows/publish.yml index 546af45e..503f9f6c 100644 --- a/{{ cookiecutter.__project_name_kebab_case }}/.github/workflows/publish.yml +++ b/{{ cookiecutter.__project_name_kebab_case }}/.github/workflows/publish.yml @@ -14,7 +14,7 @@ jobs: uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "{{ cookiecutter.python_version }}" diff --git a/{{ cookiecutter.__project_name_kebab_case }}/.github/workflows/test.yml b/{{ cookiecutter.__project_name_kebab_case }}/.github/workflows/test.yml index 48ad286b..2d5e58d6 100644 --- a/{{ cookiecutter.__project_name_kebab_case }}/.github/workflows/test.yml +++ b/{{ cookiecutter.__project_name_kebab_case }}/.github/workflows/test.yml @@ -42,6 +42,6 @@ jobs: run: devcontainer exec --workspace-folder . poe test - name: Upload coverage - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: files: reports/coverage.xml From 62bf51fcb52b310c04340c7f31e17e2545ac5d66 Mon Sep 17 00:00:00 2001 From: Sipalste <99645608+SiPaRadix@users.noreply.github.com> Date: Tue, 23 Apr 2024 13:22:00 +0200 Subject: [PATCH 08/17] fix: use correct app sequence task name (#237) --- {{ cookiecutter.__project_name_kebab_case }}/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/{{ cookiecutter.__project_name_kebab_case }}/pyproject.toml b/{{ cookiecutter.__project_name_kebab_case }}/pyproject.toml index 94775a95..56dbbc96 100644 --- a/{{ cookiecutter.__project_name_kebab_case }}/pyproject.toml +++ b/{{ cookiecutter.__project_name_kebab_case }}/pyproject.toml @@ -206,7 +206,7 @@ convention = "{{ cookiecutter.__docstring_style|lower }}" [tool.poe.tasks.app] help = "Serve the app" - [[tool.poe.tasks.lint.sequence]] + [[tool.poe.tasks.app.sequence]] cmd = "echo 'Serving app...'" {%- endif %} From f974b02152d18657cb94a9167b814f05395184a5 Mon Sep 17 00:00:00 2001 From: Laurent Sorber Date: Tue, 23 Apr 2024 13:44:51 +0200 Subject: [PATCH 09/17] chore: update Dev Container settings (#232) --- .../.devcontainer/devcontainer.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/{{ cookiecutter.__project_name_kebab_case }}/.devcontainer/devcontainer.json b/{{ cookiecutter.__project_name_kebab_case }}/.devcontainer/devcontainer.json index fb978056..0b4dd7e2 100644 --- a/{{ cookiecutter.__project_name_kebab_case }}/.devcontainer/devcontainer.json +++ b/{{ cookiecutter.__project_name_kebab_case }}/.devcontainer/devcontainer.json @@ -28,8 +28,8 @@ "reports/coverage.xml" ], "editor.codeActionsOnSave": { - "source.fixAll": true, - "source.organizeImports": true + "source.fixAll": "explicit", + "source.organizeImports": "explicit" }, "editor.formatOnSave": true, "[python]": { @@ -44,6 +44,10 @@ "files.autoSave": "onFocusChange", "jupyter.kernels.excludePythonEnvironments": ["/usr/local/bin/python"], "mypy-type-checker.importStrategy": "fromEnvironment", + "notebook.codeActionsOnSave": { + "notebook.source.fixAll": "explicit", + "notebook.source.organizeImports": "explicit" + }, "notebook.formatOnSave.enabled": true, "python.defaultInterpreterPath": "/opt/{{ cookiecutter.__project_name_kebab_case }}-env/bin/python", "python.terminal.activateEnvironment": false, From d9d486e35a3be4789440a45300706165397ace49 Mon Sep 17 00:00:00 2001 From: Laurent Sorber Date: Tue, 23 Apr 2024 13:45:01 +0200 Subject: [PATCH 10/17] chore: upgrade to Poetry v1.8.0 (#233) --- {{ cookiecutter.__project_name_kebab_case }}/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/{{ cookiecutter.__project_name_kebab_case }}/Dockerfile b/{{ cookiecutter.__project_name_kebab_case }}/Dockerfile index 3bfb51a8..3ba3ea85 100644 --- a/{{ cookiecutter.__project_name_kebab_case }}/Dockerfile +++ b/{{ cookiecutter.__project_name_kebab_case }}/Dockerfile @@ -37,7 +37,7 @@ FROM base as poetry USER root # Install Poetry in separate venv so it doesn't pollute the main venv. -ENV POETRY_VERSION 1.6.1 +ENV POETRY_VERSION 1.8.0 ENV POETRY_VIRTUAL_ENV /opt/poetry-env RUN --mount=type=cache,target=/root/.cache/pip/ \ python -m venv $POETRY_VIRTUAL_ENV && \ From a969f1d182ec39d7d27ccb1116cf60ba736adcfa Mon Sep 17 00:00:00 2001 From: Laurent Sorber Date: Tue, 23 Apr 2024 13:45:13 +0200 Subject: [PATCH 11/17] docs: improve cruft update instructions (#235) --- README.md | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 9831df29..6bce2462 100644 --- a/README.md +++ b/README.md @@ -44,18 +44,29 @@ To create a new Python project with this template: cruft create -f https://github.com/radix-ai/poetry-cookiecutter ``` -4. _Optional:_ if your repository name differs from your project's slugified name (see `project_name` in the [Template parameters](https://github.com/radix-ai/poetry-cookiecutter#-template-parameters) below), you will need to copy the scaffolded project into the repository with: +
- ```sh - cp -r {project-name}/ {repository-name}/ - ``` + ⚠️ If your repository name ≠ the project's slugified name + + If your repository name differs from your project's slugified name (see `project_name` in the [Template parameters](https://github.com/radix-ai/poetry-cookiecutter#-template-parameters) below), you will need to copy the scaffolded project into the repository with: + + ```sh + cp -r {project-name}/ {repository-name}/ + ``` + +
### Updating your Python project -To update your Python project with the latest template: +To update your Python project to the latest template version: + +1. Update the project while verifying the existing template parameters and setting any new parameters, if there are any: + + ```sh + cruft update --cookiecutter-input + ``` -1. Run `cruft update` to update your project with the latest template. -2. If any of the updates failed, resolve them by inspecting the generated `.rej` files. +2. If any of the file updates failed, resolve them by inspecting the corresponding `.rej` files. ## 🤓 Template parameters From aa2e59ebb22923291c841a3bd59843d9164c7d5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Go=C3=A1s=20Aguililla?= Date: Fri, 3 May 2024 06:47:45 +0200 Subject: [PATCH 12/17] chore: don't strip whitespace before header (#240) --- {{ cookiecutter.__project_name_kebab_case }}/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/{{ cookiecutter.__project_name_kebab_case }}/README.md b/{{ cookiecutter.__project_name_kebab_case }}/README.md index 509a5fa0..586039f4 100644 --- a/{{ cookiecutter.__project_name_kebab_case }}/README.md +++ b/{{ cookiecutter.__project_name_kebab_case }}/README.md @@ -22,7 +22,7 @@ To view the CLI help information, run: ```sh {{ cookiecutter.__project_name_kebab_case }} --help ``` -{%- elif cookiecutter.project_type == "app" -%} +{%- elif cookiecutter.project_type == "app" %} To serve this app, run: From 08c7fcd740fc0807e7de1810950c40fc4ac93013 Mon Sep 17 00:00:00 2001 From: Sipalste <99645608+SiPaRadix@users.noreply.github.com> Date: Tue, 11 Jun 2024 13:35:01 +0200 Subject: [PATCH 13/17] fix: avoid dubious ownership git warning (#242) --- {{ cookiecutter.__project_name_kebab_case }}/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/{{ cookiecutter.__project_name_kebab_case }}/Dockerfile b/{{ cookiecutter.__project_name_kebab_case }}/Dockerfile index 3ba3ea85..c775fb93 100644 --- a/{{ cookiecutter.__project_name_kebab_case }}/Dockerfile +++ b/{{ cookiecutter.__project_name_kebab_case }}/Dockerfile @@ -75,6 +75,7 @@ RUN --mount=type=cache,target=/var/cache/apt/ \ sh -c "$(curl -fsSL https://starship.rs/install.sh)" -- "--yes" && \ usermod --shell /usr/bin/zsh user && \ echo 'user ALL=(root) NOPASSWD:ALL' > /etc/sudoers.d/user && chmod 0440 /etc/sudoers.d/user +RUN git config --system --add safe.directory '*' USER user # Install the development Python dependencies in the virtual environment. From 7e5c8b02306de97e764ff6acd70e95ee1ec602a9 Mon Sep 17 00:00:00 2001 From: awheat Date: Tue, 2 Apr 2024 09:39:11 -0400 Subject: [PATCH 14/17] Settings to include Zscaler cert --- README.md | 37 +++++++++++-------- cookiecutter.json | 3 ++ .../.devcontainer/devcontainer.json | 5 +++ .../Dockerfile | 6 +++ 4 files changed, 35 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 6bce2462..1d17aef4 100644 --- a/README.md +++ b/README.md @@ -70,19 +70,24 @@ To update your Python project to the latest template version: ## 🤓 Template parameters -| Parameter | Description | -| ------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `project_type`
["package", "app"] | Whether the project is a publishable Python package or a deployable Python app. | -| `project_name`
"Spline Reticulator" | The name of the project. Will be slugified to `snake_case` for importing and `kebab-case` for installing. For example, `My Package` will be `my_package` for importing and `my-package` for installing. | -| `project_description`
"A Python package that reticulates splines." | A single-line description of the project. | -| `project_url`
"" | The URL to the project's repository. | -| `author_name`
"John Smith" | The full name of the primary author of the project. | -| `author_email`
"" | The email address of the primary author of the project. | -| `python_version`
"3.10" | The minimum Python version that the project requires. | -| `development_environment`
["simple", "strict"] | Whether to configure the development environment with a focus on simplicity or with a focus on strictness. In strict mode, additional [Ruff rules](https://beta.ruff.rs/docs/rules/) are added, and tools such as [Mypy](https://github.com/python/mypy) and [Pytest](https://github.com/pytest-dev/pytest) are set to strict mode. | -| `with_conventional_commits`
["0", "1"] | If "1", [Commitizen](https://github.com/commitizen-tools/commitizen) will verify that your commits follow the [Conventional Commits](https://www.conventionalcommits.org/) standard. In return, `cz bump` may be used to automate [Semantic Versioning](https://semver.org/) and [Keep A Changelog](https://keepachangelog.com/). | -| `with_fastapi_api`
["0", "1"] | If "1", [FastAPI](https://github.com/tiangolo/fastapi) is added as a run time dependency, FastAPI API stubs and tests are added, a `poe api` command for serving the API is added. | -| `with_typer_cli`
["0", "1"] | If "1", [Typer](https://github.com/tiangolo/typer) is added as a run time dependency, Typer CLI stubs and tests are added, the package itself is registered as a CLI. | -| `continuous_integration`
["GitHub", "GitLab"] | Whether to include a [GitHub Actions](https://docs.github.com/en/actions) or a [GitLab CI/CD](https://docs.gitlab.com/ee/ci/) continuous integration workflow for testing the project, and publishing the package or deploying the app. | -| `private_package_repository_name`
"Private Package Repository" | Optional name of a private package repository to install packages from and publish this package to. | -| `private_package_repository_url`
"" | Optional URL of a private package repository to install packages from and publish this package to. Make sure to include the `/simple` suffix. For instance, when using a GitLab Package Registry this value should be of the form `https://gitlab.com/api/v4/projects/` `{project_id}` `/packages/pypi/simple`. | +| Parameter | Description | +| ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `package_name`
"Spline Reticulator" | The name of the package. Will be slugified to `snake_case` for importing and `kebab-case` for installing. For example, `My Package` will be `my_package` for importing and `my-package` for installing. | +| `package_description`
"A Python package that reticulates splines." | A single-line description of the package. | +| `package_url`
"https://github.com/user/spline-reticulator" | The URL to the package's repository. | +| `author_name`
"John Smith" | The full name of the primary author of the package. | +| `author_email`
"john@example.com" | The email address of the primary author of the package. | +| `python_version`
"3.8" | The minimum Python version that the package requires. | +| `docker_image`
"python:$PYTHON_VERSION-slim" | The base Docker image to use for the Dev Container and application. The $PYTHON_VERSION build argument is equal to the `python_version` value by default, but may be overridden when building the image to test different Python versions. If CUDA support is required, you may use [radixai/python-gpu:$PYTHON_VERSION-cuda11.8](https://github.com/radix-ai/python-gpu). +| `with_zscaler_cert`
["0", "1"] | Include the ZScaler CA Root Certificate in the devcontainer. | | +| `development_environment`
["simple", "strict"] | Whether to configure the development environment with a focus on simplicity or with a focus on strictness. In strict mode, additional [Ruff rules](https://beta.ruff.rs/docs/rules/) are added, and tools such as [Mypy](https://github.com/python/mypy) and [Pytest](https://github.com/pytest-dev/pytest) are set to strict mode. | +| `with_conventional_commits`
["0", "1"] | If "1", [Commitizen](https://github.com/commitizen-tools/commitizen) will verify that your commits follow the [Conventional Commits](https://www.conventionalcommits.org/) standard. In return, `cz bump` may be used to automate [Semantic Versioning](https://semver.org/) and [Keep A Changelog](https://keepachangelog.com/). | +| `with_fastapi_api`
["0", "1"] | If "1", [FastAPI](https://github.com/tiangolo/fastapi) is added as a run time dependency, FastAPI API stubs and tests are added, a `poe api` command for serving the API is added, and an `app` stage that packages the API is added to the Dockerfile. Additionally, the CI workflow will push the application as a Docker image instead of publishing the Python package. | +| `with_pydantic_typing`
["0", "1"] | If "1", [Pydantic](https://github.com/samuelcolvin/pydantic) is added as a run time dependency, and the [Pydantic mypy plugin](https://pydantic-docs.helpmanual.io/mypy_plugin/) is enabled and configured. | +| `with_sentry_logging`
["0", "1"] | If "1", [Sentry](https://github.com/getsentry/sentry-python) is added as a run time dependency, and a Sentry configuration stub and tests are added. | +| `with_streamlit_app`
["0", "1"] | If "1", [Streamlit](https://github.com/streamlit/streamlit) is added as a run time dependency, a Streamlit application stub is added, a `poe app` command to serve the Streamlit app is added, and an `app` stage that packages the Streamlit app is added to the Dockerfile. Additionally, the CI workflow will push the application as a Docker image instead of publishing the Python package. | +| `with_typer_cli`
["0", "1"] | If "1", [Typer](https://github.com/tiangolo/typer) is added as a run time dependency, Typer CLI stubs and tests are added, the package itself is registered as a CLI, and an `app` stage is added to the Dockerfile that packages the CLI. | +| `continuous_integration`
["GitHub", "GitLab"] | Whether to include a [GitHub Actions](https://docs.github.com/en/actions) or a [GitLab CI/CD](https://docs.gitlab.com/ee/ci/) continuous integration workflow for testing and publishing the package or app. | +| `docstring_style`
["NumPy", "Google"] | Whether to use and validate [NumPy-style](https://numpydoc.readthedocs.io/en/latest/format.html) or [Google-style docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings). | +| `private_package_repository_name`
"Private Package Repository" | Optional name of a private package repository to install packages from and publish this package to. | +| `private_package_repository_url`
"https://pypi.example.com/simple" | Optional URL of a private package repository to install packages from and publish this package to. Make sure to include the `/simple` suffix. For instance, when using a GitLab Package Registry this value should be of the form `https://gitlab.com/api/v4/projects/` `{project_id}` `/packages/pypi/simple`. | diff --git a/cookiecutter.json b/cookiecutter.json index f548f9a9..c405e9b9 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -8,6 +8,9 @@ "project_url": "https://github.com/user/my-{{ cookiecutter.project_type }}", "author_name": "John Smith", "author_email": "john@example.com", + "docker_image": "python:$PYTHON_VERSION-slim", + "with_zscaler_cert": "1", + "development_environment": ["simple", "strict"], "python_version": "{% if cookiecutter.project_type == 'app' %}3.12{% else %}3.10{% endif %}", "development_environment": [ "simple", diff --git a/{{ cookiecutter.__project_name_kebab_case }}/.devcontainer/devcontainer.json b/{{ cookiecutter.__project_name_kebab_case }}/.devcontainer/devcontainer.json index 0b4dd7e2..d9660f79 100644 --- a/{{ cookiecutter.__project_name_kebab_case }}/.devcontainer/devcontainer.json +++ b/{{ cookiecutter.__project_name_kebab_case }}/.devcontainer/devcontainer.json @@ -6,6 +6,11 @@ "remoteUser": "user", "overrideCommand": true, "postStartCommand": "cp --update /opt/build/poetry/poetry.lock /workspaces/${localWorkspaceFolderBasename}/ && mkdir -p /workspaces/${localWorkspaceFolderBasename}/.git/hooks/ && cp --update /opt/build/git/* /workspaces/${localWorkspaceFolderBasename}/.git/hooks/", + {%- if cookiecutter.with_zscaler_cert|int %} + "containerEnv": { + "NODE_EXTRA_CA_CERTS": "/usr/local/share/ca-certificates/zscaler_root.crt" + }, + {%- endif %} "customizations": { "vscode": { "extensions": [ diff --git a/{{ cookiecutter.__project_name_kebab_case }}/Dockerfile b/{{ cookiecutter.__project_name_kebab_case }}/Dockerfile index c775fb93..fdfd64da 100644 --- a/{{ cookiecutter.__project_name_kebab_case }}/Dockerfile +++ b/{{ cookiecutter.__project_name_kebab_case }}/Dockerfile @@ -13,6 +13,12 @@ ENV PYTHONFAULTHANDLER 1 ENV PYTHONUNBUFFERED 1 {%- endif %} +{%- if cookiecutter.with_zscaler_cert|int %} +# Add the ZScaler CA Cert to the devcontainer if configured. +ADD zscaler_root.crt /usr/local/share/ca-certificates/zscaler_root.crt +RUN chmod 644 /usr/local/share/ca-certificates/zscaler_root.crt && update-ca-certificates +{%- endif %} + # Create a non-root user and switch to it [1]. # [1] https://code.visualstudio.com/remote/advancedcontainers/add-nonroot-user ARG UID=1000 From 76f0f9ce0196abcbaaa217eed8cdeef0712f3aac Mon Sep 17 00:00:00 2001 From: awheat Date: Tue, 2 Apr 2024 09:42:48 -0400 Subject: [PATCH 15/17] ZScaler cert file --- .../zscaler_root.crt | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 {{ cookiecutter.__project_name_kebab_case }}/zscaler_root.crt diff --git a/{{ cookiecutter.__project_name_kebab_case }}/zscaler_root.crt b/{{ cookiecutter.__project_name_kebab_case }}/zscaler_root.crt new file mode 100644 index 00000000..45e3a29f --- /dev/null +++ b/{{ cookiecutter.__project_name_kebab_case }}/zscaler_root.crt @@ -0,0 +1,28 @@ +-----BEGIN CERTIFICATE----- +MIIE0zCCA7ugAwIBAgIJANu+mC2Jt3uTMA0GCSqGSIb3DQEBCwUAMIGhMQswCQYD +VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTERMA8GA1UEBxMIU2FuIEpvc2Ux +FTATBgNVBAoTDFpzY2FsZXIgSW5jLjEVMBMGA1UECxMMWnNjYWxlciBJbmMuMRgw +FgYDVQQDEw9ac2NhbGVyIFJvb3QgQ0ExIjAgBgkqhkiG9w0BCQEWE3N1cHBvcnRA +enNjYWxlci5jb20wHhcNMTQxMjE5MDAyNzU1WhcNNDIwNTA2MDAyNzU1WjCBoTEL +MAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExETAPBgNVBAcTCFNhbiBK +b3NlMRUwEwYDVQQKEwxac2NhbGVyIEluYy4xFTATBgNVBAsTDFpzY2FsZXIgSW5j +LjEYMBYGA1UEAxMPWnNjYWxlciBSb290IENBMSIwIAYJKoZIhvcNAQkBFhNzdXBw +b3J0QHpzY2FsZXIuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +qT7STSxZRTgEFFf6doHajSc1vk5jmzmM6BWuOo044EsaTc9eVEV/HjH/1DWzZtcr +fTj+ni205apMTlKBW3UYR+lyLHQ9FoZiDXYXK8poKSV5+Tm0Vls/5Kb8mkhVVqv7 +LgYEmvEY7HPY+i1nEGZCa46ZXCOohJ0mBEtB9JVlpDIO+nN0hUMAYYdZ1KZWCMNf +5J/aTZiShsorN2A38iSOhdd+mcRM4iNL3gsLu99XhKnRqKoHeH83lVdfu1XBeoQz +z5V6gA3kbRvhDwoIlTBeMa5l4yRdJAfdpkbFzqiwSgNdhbxTHnYYorDzKfr2rEFM +dsMU0DHdeAZf711+1CunuQIDAQABo4IBCjCCAQYwHQYDVR0OBBYEFLm33UrNww4M +hp1d3+wcBGnFTpjfMIHWBgNVHSMEgc4wgcuAFLm33UrNww4Mhp1d3+wcBGnFTpjf +oYGnpIGkMIGhMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTERMA8G +A1UEBxMIU2FuIEpvc2UxFTATBgNVBAoTDFpzY2FsZXIgSW5jLjEVMBMGA1UECxMM +WnNjYWxlciBJbmMuMRgwFgYDVQQDEw9ac2NhbGVyIFJvb3QgQ0ExIjAgBgkqhkiG +9w0BCQEWE3N1cHBvcnRAenNjYWxlci5jb22CCQDbvpgtibd7kzAMBgNVHRMEBTAD +AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAw0NdJh8w3NsJu4KHuVZUrmZgIohnTm0j+ +RTmYQ9IKA/pvxAcA6K1i/LO+Bt+tCX+C0yxqB8qzuo+4vAzoY5JEBhyhBhf1uK+P +/WVWFZN/+hTgpSbZgzUEnWQG2gOVd24msex+0Sr7hyr9vn6OueH+jj+vCMiAm5+u +kd7lLvJsBu3AO3jGWVLyPkS3i6Gf+rwAp1OsRrv3WnbkYcFf9xjuaf4z0hRCrLN2 +xFNjavxrHmsH8jPHVvgc1VD0Opja0l/BRVauTrUaoW6tE+wFG5rEcPGS80jjHK4S +pB5iDj2mUZH1T8lzYtuZy0ZPirxmtsk3135+CKNa2OCAhhFjE0xd +-----END CERTIFICATE----- From 9ea9c830cd6c3e053665ec2511efcb92a5c86715 Mon Sep 17 00:00:00 2001 From: awheat Date: Sat, 4 May 2024 12:44:58 -0400 Subject: [PATCH 16/17] Updates to template --- .../.devcontainer/devcontainer.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/{{ cookiecutter.__project_name_kebab_case }}/.devcontainer/devcontainer.json b/{{ cookiecutter.__project_name_kebab_case }}/.devcontainer/devcontainer.json index d9660f79..ed4edb32 100644 --- a/{{ cookiecutter.__project_name_kebab_case }}/.devcontainer/devcontainer.json +++ b/{{ cookiecutter.__project_name_kebab_case }}/.devcontainer/devcontainer.json @@ -26,7 +26,6 @@ "ms-toolsai.jupyter", "ryanluker.vscode-coverage-gutters", "tamasfe.even-better-toml", - "visualstudioexptteam.vscodeintellicode" ], "settings": { "coverage-gutters.coverageFileNames": [ @@ -36,12 +35,12 @@ "source.fixAll": "explicit", "source.organizeImports": "explicit" }, - "editor.formatOnSave": true, + "editor.formatOnSave": "explicit", "[python]": { "editor.defaultFormatter": "charliermarsh.ruff" }, "[toml]": { - "editor.formatOnSave": false + "editor.formatOnSave": "explicit" }, "editor.rulers": [ 100 From 1a7556b9dbe687691ae6dbb701cc180d30fade01 Mon Sep 17 00:00:00 2001 From: awheat Date: Wed, 24 Jul 2024 20:33:35 -0400 Subject: [PATCH 17/17] updated extensions --- .../.devcontainer/devcontainer.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/{{ cookiecutter.__project_name_kebab_case }}/.devcontainer/devcontainer.json b/{{ cookiecutter.__project_name_kebab_case }}/.devcontainer/devcontainer.json index ed4edb32..dda8317d 100644 --- a/{{ cookiecutter.__project_name_kebab_case }}/.devcontainer/devcontainer.json +++ b/{{ cookiecutter.__project_name_kebab_case }}/.devcontainer/devcontainer.json @@ -26,6 +26,8 @@ "ms-toolsai.jupyter", "ryanluker.vscode-coverage-gutters", "tamasfe.even-better-toml", + "GitHub.copilot-chat", + "GitHub.copilot" ], "settings": { "coverage-gutters.coverageFileNames": [