diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index a9ae156..2df9bff 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,21 +1,22 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the -// README at: https://github.com/devcontainers/templates/tree/main/src/python { - "name": "Python 3", - "build": { "dockerfile": "Dockerfile" }, - - // Features to add to the dev container. More info: https://containers.dev/features. - // "features": {}, - - // Use 'forwardPorts' to make a list of ports inside the container available locally. - // "forwardPorts": [], - - // Use 'postCreateCommand' to run commands after the container is created. - // "postCreateCommand": "pip3 install --user -r requirements.txt", - - // Configure tool-specific properties. - // "customizations": {}, - - // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. - "remoteUser": "root" + "name": "project", + "image": "mcr.microsoft.com/devcontainers/python:3", + "features": { + "ghcr.io/devcontainers-extra/features/mise:1": {} + }, + "customizations": { + "vscode": { + "extensions": [ + "hverlin.mise-vscode", + "tamasfe.even-better-toml", + "redhat.vscode-yaml", + "charliermarsh.ruff", + "ms-python.python" + ] + } + }, + "postCreateCommand": "echo 'eval \"$(mise activate bash)\"' >> ~/.bashrc", + "forwardPorts": [ + "8000:8000" + ] } diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 0f5b7c1..05fecfe 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -15,14 +15,13 @@ jobs: - "3.12" steps: - uses: actions/checkout@v6 - - name: Install uv and setup Python - uses: astral-sh/setup-uv@v7 + - uses: jdx/mise-action@v4 with: - python-version: ${{ matrix.python-version }} - enable-cache: true + tool_versions: | + python ${{ matrix.python-version }} - name: Install dependencies - run: make install + run: mise run install - name: Format and lint - run: make lint + run: mise run lint - name: Run tests - run: make test \ No newline at end of file + run: mise run test diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index c357512..ce70982 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -11,22 +11,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - - name: Setup Python - uses: actions/setup-python@v6 - with: - python-version: 3.12 + - uses: jdx/mise-action@v4 - name: Configure Git Credentials run: | git config user.name github-actions[bot] git config user.email 41898282+github-actions[bot]@users.noreply.github.com - - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV - - uses: actions/cache@v5 - with: - key: mkdocs-material-${{ env.cache_id }} - path: .cache - restore-keys: | - mkdocs-material- - - name: Install dependencies - run: pip install mkdocs-material mkdocstrings[python] - name: Publish to GitHub Pages - run: mkdocs gh-deploy --force \ No newline at end of file + run: mise run docs diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c238608..cc6cf63 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,22 @@ repos: - - repo: local + # General hygiene + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 hooks: - - id: lint - name: Linting - entry: make lint - language: system + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-toml + - id: check-json + - id: check-merge-conflict + - id: check-added-large-files + args: [--maxkb=1000] + - id: no-commit-to-branch + args: [--branch, main] + + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.15.12 + hooks: + - id: ruff + args: [--fix] + - id: ruff-format diff --git a/.vscode/settings.json b/.vscode/settings.json index 082b194..7922cb7 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,45 @@ { - "makefile.configureOnOpen": false -} \ No newline at end of file + "python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python", + "editor.defaultFormatter": "charliermarsh.ruff", + "editor.formatOnSave": true, + "[python]": { + "editor.defaultFormatter": "charliermarsh.ruff", + "editor.codeActionsOnSave": { + "source.fixAll.ruff": "explicit", + "source.organizeImports.ruff": "explicit" + } + }, + "[toml]": { + "editor.defaultFormatter": "tamasfe.even-better-toml" + }, + "[yaml]": { + "editor.defaultFormatter": "redhat.vscode-yaml" + }, + "python.testing.pytestEnabled": true, + "python.testing.unittestEnabled": false, + "python.testing.pytestArgs": [ + "--tb=short" + ], + "files.exclude": { + "**/__pycache__": true, + "**/*.pyc": true, + "**/.pytest_cache": true, + "**/.ruff_cache": true, + "**/cdk.out": true, + "**/.venv": true + }, + "search.exclude": { + "**/__pycache__": true, + "**/.venv": true, + "**/cdk.out": true, + "**/node_modules": true, + "uv.lock": true + }, + "editor.rulers": [ + 120 + ], + "editor.insertSpaces": true, + "editor.tabSize": 4, + "files.trimTrailingWhitespace": true, + "files.insertFinalNewline": true +} diff --git a/AGENTS.md b/AGENTS.md index 74cc57e..13a13aa 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -17,6 +17,7 @@ This is a **Python project template** that provides a pre-configured, production - **Documentation** via [MkDocs](https://www.mkdocs.org) with [mkdocstrings](https://mkdocstrings.github.io), auto-deployed to GitHub Pages - **CI/CD** via GitHub Actions - **Containerisation** via Docker and Dev Containers +- **Environment & Task Management** via [mise](https://mise.jdx.dev) --- @@ -30,7 +31,7 @@ This is a **Python project template** that provides a pre-configured, production │ ├── README.md # Project README and MkDocs home page │ ├── CONTRIBUTING.md # Contributing guidelines │ └── reference/ # Auto-generated API reference pages -├── project/ # Main source package (renamed via `make project`) +├── project/ # Main source package (renamed via `mise run project`) │ ├── __init__.py │ └── app.py # CLI entry point ├── tests/ # Test suite @@ -38,13 +39,13 @@ This is a **Python project template** that provides a pre-configured, production │ └── test_app.py # Sample tests ├── compose.yml # Docker Compose file ├── Dockerfile # App container -├── Makefile # Workflow automation targets +├── mise.toml # Workflow automation tasks ├── mkdocs.yml # MkDocs configuration ├── pyproject.toml # Project metadata, dependencies, and tool configuration └── uv.lock # Locked dependency versions (do not edit manually) ``` -> **Note:** The `project/` folder is the template placeholder. After initialising a real project with `make project NAME=...`, it is renamed to the chosen package name. +> **Note:** The `project/` folder is the template placeholder. After initialising a real project with `mise run project name=...`, it is renamed to the chosen package name. --- @@ -52,49 +53,40 @@ This is a **Python project template** that provides a pre-configured, production ### Prerequisites -- Python 3.12+ -- [pipx](https://pipx.pypa.io) (to install uv) +- [mise](https://mise.jdx.dev) - Docker (for Dev Container or containerised runs) ### First-time setup ```bash -# 1. Install uv (if not already installed) -make uv - -# 2. Install all dependencies -make install - -# 3. Install pre-commit hooks -make precommit - -# 4. Activate the virtual environment -make venv +# Install tools, project dependencies, and pre-commit hooks +mise run dev ``` ### Rename the template for a new project (run once) ```bash -make project NAME="my-project" DESCRIPTION="My app" AUTHOR="Your Name" EMAIL="you@example.com" GITHUB="your-username" +mise run project --name "my-project" --description "My app" --author "Your Name" --email "you@example.com" --github "your-username" ``` --- ## Common Commands -| Task | Command | -|---|---| -| Install dependencies | `make install` | -| Update dependencies | `make update` | -| Lint and format | `make lint` | -| Run tests with coverage | `make test` | -| Run app locally | `app` (after `make venv`) or `uv run app` | -| Run app in Docker | `docker compose run app` | -| Serve docs locally | `make local` | -| Deploy docs to GitHub Pages | `make docs` | -| Full setup from scratch | `make all` | - -> Refer to `docs/README.md` for the full list of available targets. Add new targets to `Makefile` as needed. +| Task | Command | Alias | +|---|---|---| +| Install dependencies | `mise run install` | `i` | +| Update dependencies | `mise run update` | `u` | +| Lint and format | `mise run lint` | `l` | +| Run tests with coverage | `mise run test` | `t` | +| Run app locally | `mise run app` | `a` | +| Run app in Docker | `docker compose run app` | | +| Serve docs locally | `mise run local-docs` | `d` | +| Deploy docs to GitHub Pages | `mise run docs` | | +| Setup dev environment | `mise run dev` | | +| Full setup from scratch | `mise run all` | | + +> Refer to `docs/README.md` for the full list of available targets. Add new targets to `mise.toml` as needed. --- @@ -104,7 +96,7 @@ make project NAME="my-project" DESCRIPTION="My app" AUTHOR="Your Name" EMAIL="yo - **Linter/formatter**: `ruff` — enforces `E` (pycodestyle errors) and `I` (isort) rules - **Type checker**: `pyright` — all imports must resolve; missing imports are errors - **Pre-commit**: hooks run `ruff` automatically before every commit -- Run `make lint` to format, sort imports, and check types manually +- Run `mise run lint` to format, sort imports, and check types manually - All public functions and classes must have docstrings (used by `mkdocstrings` for API docs) --- @@ -112,7 +104,7 @@ make project NAME="my-project" DESCRIPTION="My app" AUTHOR="Your Name" EMAIL="yo ## Testing - Tests live in the `tests/` directory and mirror the source structure -- Run the full test suite with coverage using `make test` (runs `pytest` via `coverage`) +- Run the full test suite with coverage using `mise run test` (runs `pytest` via `coverage`) - Coverage is measured with branch coverage enabled; the report is printed to the terminal and exported as `coverage.xml` - Shared fixtures belong in `tests/conftest.py` - Test files must be named `test_*.py` @@ -140,15 +132,15 @@ make project NAME="my-project" DESCRIPTION="My app" AUTHOR="Your Name" EMAIL="yo - Use `pytest` monkeypatch and `pytest-mock` for mocking instead of `unittest.MagicMock` - Do not cheat! Never modify source code just to make a failing test pass. Fix real bugs in source code and fix incorrect assertions in tests -## Make Targets +## Mise Tasks -Use `make` targets for all common workflows: lint, test, run locally, and deploy. Refer to `docs/README.md` for currently available targets. Add new targets to `Makefile` as needed. +Use `mise` tasks for all common workflows: lint, test, run locally, and deploy. Refer to `docs/README.md` for currently available tasks. Add new tasks to `mise.toml` as needed. ## Notes - Python 3.12+ required - Dependencies are managed via `pyproject.toml` and locked in `uv.lock` -- Do not edit `uv.lock` directly; use `make update` to update dependencies +- Do not edit `uv.lock` directly; use `mise run update` to update dependencies ## Coding Conventions diff --git a/Dockerfile b/Dockerfile index b347ea4..d43a75b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,6 @@ -FROM python:3.12-alpine +FROM python:3.14-alpine COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ WORKDIR /code -RUN apk add -u make COPY . . -RUN make install +RUN uv sync ENTRYPOINT ["uv", "run", "app"] diff --git a/Makefile b/Makefile deleted file mode 100644 index 1c8d0a3..0000000 --- a/Makefile +++ /dev/null @@ -1,58 +0,0 @@ -.DEFAULT_GOAL := help - -NAME ?= project -DESCRIPTION ?= Python Project Template -AUTHOR ?= Amr Abed -EMAIL ?= amrabed -GITHUB ?= amrabed - -.PHONY: help -help: # Show help - @grep -E '^[a-zA-Z_-]+:.*?# .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?# "}; {printf "\033[36m%-15s\033[0m %s\n", $$1, $$2}' - -.PHONY: project -project: uv # Rename project (run once) - @uv run rename \ - --name '$(subst ','\'',$(NAME))' \ - --description '$(subst ','\'',$(DESCRIPTION))' \ - --author '$(subst ','\'',$(AUTHOR))' \ - --email '$(subst ','\'',$(EMAIL))' \ - --github '$(subst ','\'',$(GITHUB))' - -uv: # Install uv - @command -v uv >/dev/null 2>&1 || pipx install uv - -venv: # Create and activate virtual environment and install dependencies - uv sync - -install: venv # Install dependencies and project - -update: # Update dependencies - uv lock --upgrade - -precommit: # Install pre-commit hooks - uv run pre-commit autoupdate - uv run pre-commit install - -pre-commit: precommit - -lint: # Lint code - uv run ruff check --fix - uv run ruff format - uv run pyright - -coverage: - uv run coverage run -m pytest . - uv run coverage report -m - uv run coverage xml - -test: coverage - -.PHONY: docs -docs: # Build and deploy documentation to GitHub pages - uv run mkdocs gh-deploy --force - -local: # Serve documentation on a local server - uv run mkdocs serve - -all: install lint test diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index dc8c960..dffb8c9 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -29,19 +29,17 @@ To make contributing as easy and fast as possible, you'll want to run tests and You'll need the following prerequisites: -- **Python 3.12+** -- **uv** +- [mise](https://mise.jdx.dev) - **git** -- **make** ### How to contribute - Fork the repository on GitHub - Clone your fork locally. -- Install the project dependencies: +- Install the project dependencies and setup the dev environment: ```bash -make uv install pre-commit +mise run dev ``` - Create a new branch (with a descriptive name) for your changes: @@ -52,7 +50,7 @@ git checkout -b my-new-feature # use descriptive branch name - Run tests and linting locally to make sure everything is working as expected. ```bash -make lint test +mise run lint test ``` - Commit your changes and push your branch to GitHub ```bash @@ -82,7 +80,7 @@ Project Documentation is written in Markdown and built using [Material for MkDoc To preview the docuementation on your local, run: ```bash -make local +mise run local-docs ``` ## Reporting Bugs diff --git a/docs/README.md b/docs/README.md index e5f51ff..c580ddb 100644 --- a/docs/README.md +++ b/docs/README.md @@ -6,6 +6,7 @@ A Python project template that comes out of the box with configuration for: - Packaging and dependency management using [uv](https://docs.astral.sh/uv) +- Development environment and task management using [mise](https://mise.jdx.dev) - Command Line Interface (CLI) using [click](https://click.palletsprojects.com) - Testing using [pytest](https://pytest.org) - Code coverage using [coverage](https://coverage.readthedocs.io) @@ -41,18 +42,17 @@ Click this button to create a new repository for your project, then clone the ne ### Rename the project After cloning the repository, rename the project by running: ```bash -make project NAME="" DESCRIPTION="" AUTHOR="" EMAIL="" GITHUB="" SOURCE="" +mise run project --name "" --description "" --author "" --email "" --github "" ``` Pass the following parameters: Parameter | Description --- | --- -`NAME` | Project new name -`DESCRIPTION` | Project short description -`SOURCE` | (optional) Source folder name -`AUTHOR` | Author name -`EMAIL`| Author email -`GITHUB`| GitHub username (for GitHub funding) +`name` | Project new name +`description` | Project short description +`author` | Author name +`email`| Author email +`github`| GitHub username (for GitHub funding) ## Prerequisites @@ -60,59 +60,47 @@ Parameter | Description - Docker ### Local environment -- Python 3.12+ (You can update the [`pyproject.toml`](../pyproject.toml#L39) for lower versions) -- Pipx (*optional* - used to install uv if not already installed) +- [mise](https://mise.jdx.dev) ## Setup -### Install uv -To install uv, if not installed (requires pipx), run: -```bash -make uv -``` - ### Install / Update dependencies -To install the project dependencies defined in the [pyproject.toml](../pyproject.toml) file, run: +To install the project dependencies, run: ```bash -make install +mise run install ``` To update the project dependencies, run: ```bash -make update +mise run update ``` ### Install pre-commit hooks To install the pre-commit hooks for the project to format and lint your code automatically before commiting, run: ```bash -make precommit +mise run precommit ``` -### Activate virtual environemnt -To activate the virtual environment, run: -```bash -make venv -``` +### Activate virtual environment +Mise automatically creates and activates the virtual environment if you have `mise activate` set up in your shell. Otherwise, you can run tasks using `mise run ` or commands using `mise exec -- `. ### Format and Lint code To format and lint project code, run: ```bash -make lint +mise run lint ``` ### Run tests with coverage To run the unit tests defined under the [tests](../tests/) folder and show coverage report, run: ```bash -make test +mise run test ``` ## Running the project -A uv script, with the name `app`, is defined in the [pyproject.toml](../pyproject.toml#L36) file, to let you to run the project as a shell command. +A mise task, with the name `app`, is defined in the [mise.toml](../mise.toml) file, to let you to run the project. ### Local / Dev container -> Make sure to activate the virtual environment using `make venv` to be able to run `app` without `uv run` - -Try running `app -h` or `app --help` to get the help message of your app: +Try running `mise run app -- -h` or `mise run app -- --help` to get the help message of your app: ```bash Usage: app [OPTIONS] @@ -132,7 +120,7 @@ docker compose run app -h ## Generating documentation To generate and publish the project documentation to GitHub pages, run: ```bash -make docs +mise run docs ``` That pushes the new documentation to the gh-pages branch. Make sure GitHub Pages is enableed in your repository settings and using the gh-pages branch for the documentation to be publicly available. @@ -140,7 +128,7 @@ Make sure GitHub Pages is enableed in your repository settings and using the gh- ### Local To serve the documentation on a local server, run: ```bash -make local +mise run local-docs ``` ## Project Structure @@ -169,7 +157,7 @@ make local ├── compose.yml # Docker-compose file ├── Dockerfile # App container Dockerfile ├── LICENSE # Project license -├── Makefile # Make commands +├── mise.toml # Mise configuration and tasks ├── pyproject.toml # Configuration file for different tools ├── docs # Documentaion folder │ ├── mkdocs.yml # mkdocs configuration file diff --git a/mise.toml b/mise.toml new file mode 100644 index 0000000..450814a --- /dev/null +++ b/mise.toml @@ -0,0 +1,105 @@ +[tools] +python = "3.14" +uv = "latest" + +###### Shell aliases ###### + +[shell_alias] +m = "mise run" +i = "mise run install" +u = "mise run update" +l = "mise run lint" +t = "mise run test" +d = "mise run local-docs" + +ll = "ls -l" +la = "ls -a" + +g = "git" +gs = "git status" +ga = "git add" +gc = "git commit" +gp = "git push" + + +###### Settings ###### + +[settings] +python.uv_venv_auto = "create|source" + +###### Setup ###### +[tasks.dev] +description = "Setup the dev environment" +depends = ["precommit", "install"] + +[tasks.precommit] +description = "Install pre-commit hooks" +run = "uv run pre-commit autoupdate && uv run pre-commit install" + +[tasks.install] +description = "Install dependencies" +alias = "i" +run = "uv sync" + + +[tasks.update] +description = "Update dependencies" +alias = "u" +run = "uv lock --upgrade" + +##### Common Tasks ##### + +[tasks.lint] +description = "Lint and format code" +alias = "l" +run = [ + "uv run ruff check --fix", + "uv run ruff format", + # "uv run pyright", +] + +[tasks.test] +description = "Run tests with coverage" +alias = "t" +run = [ + "uv run coverage run -m pytest .", + "uv run coverage report -m", + "uv run coverage xml", +] + +[tasks.all] +description = "Full setup from scratch" +depends = ["install", "lint", "test"] + +##### Docs ##### + +[tasks.install-docs] +description = "Install documentation dependencies" +run = "uv sync --group docs" +hide = true + +[tasks.docs] +description = "Build and deploy documentation to GitHub pages" +depends = ["install-docs"] +run = "uv run mkdocs gh-deploy --force" + +[tasks.local-docs] +description = "Serve documentation on a local server" +alias = "d" +depends = ["install-docs"] +run = "uv run mkdocs serve" + +###### Scripts ###### + +[tasks.app] +description = "Run the app" +alias = "a" +run = "uv run app" + +[tasks.rename] +description = "Rename project (run once)" +alias = "r" +run = "uv run rename" + +[tasks.project] +alias = ["rename", "p"] diff --git a/pyproject.toml b/pyproject.toml index 02ccacb..d93165d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ name = "project" version = "0.1.0" description = "Python Project Template" readme = "docs/README.md" -authors = [{name = "Amr Abed", email = "amrabed@gmail.com"}] +authors = [{name = "Amr Abed", email = ""}] license = {text = "MIT"} requires-python = ">=3.12" dependencies = [ @@ -21,6 +21,8 @@ dev = [ "coverage>=7.6.9", "pre-commit>=4.0.1", "pyright>=1.1.391", +] +docs = [ "mkdocs>=1.6.1", "mkdocstrings[python]>=1.0.4", "mkdocs-material>=9.6.20", diff --git a/uv.lock b/uv.lock index 03869cf..ad2ec2b 100644 --- a/uv.lock +++ b/uv.lock @@ -580,14 +580,16 @@ dependencies = [ [package.dev-dependencies] dev = [ { name = "coverage" }, - { name = "mkdocs" }, - { name = "mkdocs-material" }, - { name = "mkdocstrings", extra = ["python"] }, { name = "pre-commit" }, { name = "pyright" }, { name = "pytest" }, { name = "ruff" }, ] +docs = [ + { name = "mkdocs" }, + { name = "mkdocs-material" }, + { name = "mkdocstrings", extra = ["python"] }, +] [package.metadata] requires-dist = [{ name = "click", specifier = ">=8.1.8" }] @@ -595,14 +597,16 @@ requires-dist = [{ name = "click", specifier = ">=8.1.8" }] [package.metadata.requires-dev] dev = [ { name = "coverage", specifier = ">=7.6.9" }, - { name = "mkdocs", specifier = ">=1.6.1" }, - { name = "mkdocs-material", specifier = ">=9.6.20" }, - { name = "mkdocstrings", extras = ["python"], specifier = ">=1.0.4" }, { name = "pre-commit", specifier = ">=4.0.1" }, { name = "pyright", specifier = ">=1.1.391" }, { name = "pytest", specifier = ">=9.0.1" }, { name = "ruff", specifier = ">=0.15.12" }, ] +docs = [ + { name = "mkdocs", specifier = ">=1.6.1" }, + { name = "mkdocs-material", specifier = ">=9.6.20" }, + { name = "mkdocstrings", extras = ["python"], specifier = ">=1.0.4" }, +] [[package]] name = "pygments"