Repository: FrancescoSaverioZuppichini/python-template Branch: main Commit: 06459b247e3a Files: 14 Total size: 14.5 KB Directory structure: gitextract_ov64cyve/ ├── .github/ │ └── workflows/ │ ├── build.yml │ ├── publish.yml │ └── test.yml ├── .gitignore ├── .python-version ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── examples/ │ └── main.py ├── pyproject.toml ├── src/ │ └── jokes/ │ ├── __init__.py │ └── jokes.py └── tests/ └── test_jokes.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/build.yml ================================================ # name: Create and publish a Docker image # Configures this workflow to run every time a change is pushed to the branch called `release` or a new release is created. on: release: types: [published] push: branches: ["release"] # Defines two custom environment variables for the workflow. These are used for the Container registry domain, and a name for the Docker image that this workflow builds. env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} # There is a single job in this workflow. It's configured to run on the latest available version of Ubuntu. jobs: build-and-push-image: runs-on: ubuntu-latest # Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job. permissions: contents: read packages: write attestations: write id-token: write # steps: - name: Checkout repository uses: actions/checkout@v4 # Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here. - name: Log in to the Container registry uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} # This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels. - name: Extract metadata (tags, labels) for Docker id: meta uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} # This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages. # It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository. # It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step. - name: Build and push Docker image id: push uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4 with: context: . push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} # This step generates an artifact attestation for the image, which is an unforgeable statement about where and how it was built. It increases supply chain security for people who consume the image. For more information, see "[AUTOTITLE](/actions/security-guides/using-artifact-attestations-to-establish-provenance-for-builds)." - name: Generate artifact attestation uses: actions/attest-build-provenance@v2 with: subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME}} subject-digest: ${{ steps.push.outputs.digest }} push-to-registry: true ================================================ FILE: .github/workflows/publish.yml ================================================ name: Publish on: push: branches: - main jobs: build-format-and-publish-to-pypi: runs-on: ubuntu-latest # FROM https://github.com/pypa/gh-action-pypi-publish environment: name: pypi url: https://pypi.org/p/python-template-zuppif permissions: id-token: write # IMPORTANT: this permission is mandatory for trusted publishing steps: - name: 🛎️ Checkout uses: actions/checkout@v4 - name: 📦 Install uv uses: astral-sh/setup-uv@v4 - name: 🐍 Set up Python uses: actions/setup-python@v5 with: python-version-file: ".python-version" - name: 🦾 Install the project run: uv sync - name: 💅 Format run: uv run ruff format - name: 🦾 Build run: uv build - name: 🚀 Publish uses: pypa/gh-action-pypi-publish@release/v1 ================================================ FILE: .github/workflows/test.yml ================================================ name: Test on: pull_request: branches: [main, dev] jobs: build-lint-format-and-test: runs-on: ubuntu-latest steps: - name: 🛎️ Checkout uses: actions/checkout@v4 - name: 📦 Install uv uses: astral-sh/setup-uv@v4 - name: 🐍 Set up Python uses: actions/setup-python@v5 with: python-version-file: ".python-version" - name: 🦾 Install the project run: | uv sync --all-extras --dev uv pip install -e . - name: 💅 Link & Format Check run: make check_code_quality - name: 🧪 Test run: uv run pytest tests/ ================================================ FILE: .gitignore ================================================ # ruff .ruff_cache # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover *.py,cover .hypothesis/ .pytest_cache/ cover/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 db.sqlite3-journal # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder .pybuilder/ target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv # For a library or package, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: # .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # However, in case of collaboration, if having platform-specific dependencies or dependencies # having no cross-platform support, pipenv may install dependencies that don't work, or not # install all needed dependencies. #Pipfile.lock # UV # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. # This is especially recommended for binary packages to ensure reproducibility, and is more # commonly ignored for libraries. #uv.lock # poetry # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. # This is especially recommended for binary packages to ensure reproducibility, and is more # commonly ignored for libraries. # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control #poetry.lock # pdm # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. #pdm.lock # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it # in version control. # https://pdm.fming.dev/latest/usage/project/#working-with-version-control .pdm.toml .pdm-python .pdm-build/ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm __pypackages__/ # Celery stuff celerybeat-schedule celerybeat.pid # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # Pyre type checker .pyre/ # pytype static type analyzer .pytype/ # Cython debug symbols cython_debug/ # PyCharm # JetBrains specific template is maintained in a separate JetBrains.gitignore that can # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ # PyPI configuration file .pypirc ================================================ FILE: .python-version ================================================ 3.12 ================================================ FILE: Dockerfile ================================================ # Build stage FROM python:3.12-slim AS builder # Update image RUN apt-get update && apt-get install -y --no-install-recommends \ build-essential \ && rm -rf /var/lib/apt/lists/* # Install uv COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ # Set working directory WORKDIR /app # Install the project without the the source code (only dependencies) RUN --mount=type=cache,target=/root/.cache/uv \ --mount=type=bind,source=uv.lock,target=uv.lock \ --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ uv sync --frozen --no-install-project --no-dev --compile-bytecode # Install the project's source packages ADD .python-version pyproject.toml uv.lock src/ /app/ RUN --mount=type=cache,target=/root/.cache/uv \ uv pip install --compile-bytecode . # Use alpine for the final image to reduce the total size FROM python:3.12-alpine # Copy the installed environment from builder COPY --from=builder --chown=app:app /app /app # Set working directory WORKDIR /app # Configure PATH for executables, packages are in the /app/.venv folder ENV PATH="/app/.venv/bin:$PATH" # Run command CMD ["make_me_laugh"] ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2024 Francesco Saverio Zuppichini Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Makefile ================================================ .PHONY: style check_code_quality export PYTHONPATH = . check_dirs := src style: uv run ruff format $(check_dirs) uv run ruff check --select I --fix link: uv run ruff check --fix check_code_quality: uv run ruff check $(check_dirs) # check sort uv run ruff check -e uv run ruff check --select I -e publish: uv build uv publish --token $UV_PUBLISH_TOKEN ================================================ FILE: README.md ================================================ # Python Template 🐍 [![PyPI version](https://img.shields.io/pypi/v/python-template-zuppif)](https://pypi.org/project/python-template-zuppif/) A template for a python project for 2025 Features: - [x] 🛠️ configuration in a single file [`pyproject.toml`](pyproject.toml) - [x] 📦 [`uv`](https://docs.astral.sh/uv/) as package manager - [x] 💅 [`ruff`](https://docs.astral.sh/ruff/) for linting and formatting - [x] 🧪 [`pytest`](https://docs.pytest.org/en/stable/) - [x] 🧹 [`Makefile`](Makefile) with code quality checks - [ ] 📚 auto doc generation - [x] 🐳 CI/CD Optimized Docker Image runs when a new *release* is created pushing to gh registry - [x] 🦾 GitHub actions: - [x] auto publish to [`pypi`](https://pypi.org/) on push on `main` - [ ] auto creating a new tag on push on `main`, sync versions - [x] run `tests` and `lint` on `dev` and `main` when a PR is open ## Getting started ### Installation To set it up and run ```bash uv sync ``` Then ```bash python main.py ``` Will output a random joke ``` Why did the cow in the pasture get promoted at work? ... Because he is OUT-STANDING in his field! ``` You can now run, for example, a function defined as `scripts` in the [`pyproject.toml`](pyproject.toml) ```bash make_me_laugh ``` ### Linting ``` ruff check ``` ### Formatting ``` ruff format ``` ## CI/CD ### Tests Tests inside `/tests` are run using [`pytest`](https://docs.pytest.org/en/stable/) on PR both on `dev` and `main` ### Publish Package In order to publish to [pypi](https://pypi.org/) you need to create a publisher on pypi. This is explained [here](https://packaging.python.org/en/latest/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/) and [here](https://docs.pypi.org/trusted-publishers/) In practice go your pypi project -> Manage Project -> Publishing, scroll and "add a new publisher" ### Docker [`Dockerfile`](Dockerfile) contains a multi stage build that uses `--compile-bytecode` to compite your package. For this example, the resulting image is just ```bash docker build -t python-template . ``` ``` REPOSITORY TAG IMAGE ID CREATED SIZE python-template latest 1ded7d260b1c 58 seconds ago 55.4MB ``` The image is build using the [`build`](.github/workflows/build.yml) workflow when a new *release* is created ================================================ FILE: examples/main.py ================================================ from jokes import make_me_laugh make_me_laugh() ================================================ FILE: pyproject.toml ================================================ [project] # https://docs.astral.sh/uv/concepts/projects/config/ name = "python-template-zuppif" version = "0.1.3" description = "Add your description here" readme = "README.md" requires-python = ">=3.12" dependencies = ["requests>=2.32.3"] [dependency-groups] dev = ["pytest>=8.3.4", "ruff>=0.8.4"] [build-system] requires = ["hatchling"] build-backend = "hatchling.build" [tool.hatch.build.targets.wheel] packages = ["src/jokes"] [tool.ruff] # https://docs.astral.sh/ruff/configuration/ target-version = "py312" [project.scripts] # will run the function by just typing `make_me_laugh` in the cli make_me_laugh = "jokes:make_me_laugh" ================================================ FILE: src/jokes/__init__.py ================================================ from .jokes import get_joke def make_me_laugh(): joke = get_joke() print(f"{joke["preambula"]} ... {joke['punchline']}") ================================================ FILE: src/jokes/jokes.py ================================================ from random import choice from typing import TypedDict import requests JOKES_FILE_URL = "https://gist.githubusercontent.com/FrancescoSaverioZuppichini/8e7614da3b156ccaa29d50211b790146/raw/79b00cdaa023903629052426b24cdee7212229c3/jokes.txt" class Joke(TypedDict): preambula: str punchline: str def get_joke() -> Joke: res = requests.get(JOKES_FILE_URL) content = res.text jokes = content.strip().split("\n") joke = choice(jokes) joke_splitted = joke.split("<>") return Joke(preambula=joke_splitted[0], punchline=joke_splitted[1]) ================================================ FILE: tests/test_jokes.py ================================================ from jokes import get_joke def test_jokes(): joke = get_joke() assert "preambula" in joke assert "punchline" in joke