Skip to main content

Scaffold your next Python project with one command

Project description

zenit

A CLI that scaffolds a new Python project from a template with optional addons — sets up the package structure, dev tooling, config files, and an initial git commit, then tells you what to run next.

zenit my-project

Once a project exists, zenit is still useful: add or remove addons, run a health check, and preview what any command would do before committing to it.


What it does

  1. Asks you to pick a template (blank or FastAPI)
  2. Asks you to pick addons (Docker, Redis, Celery, Sentry, GitHub Actions)
  3. Generates the project directory, all files, pyproject.toml, and justfile
  4. Runs git init and makes the first commit

It does not run uv sync, start servers, or do anything network-dependent. You get a directory you can immediately cd into and start working.


Requirements

All platforms need:

  • Python 3.14+
  • uv 0.4+install
  • git
  • justinstall (optional but the generated projects use it heavily)
  • direnv — optional, auto-activates the virtualenv on cd (see per-platform notes below)

The fastapi template additionally needs Docker running locally.


Installation

macOS

# Install uv if you haven't
curl -LsSf https://astral.sh/uv/install.sh | sh

# Install zenit
uv tool install zenit

# Or run without installing
uvx zenit my-project

direnv (recommended):

brew install direnv
# Add to ~/.zshrc or ~/.bash_profile:
eval "$(direnv hook zsh)"   # or bash

Linux (non-NixOS)

curl -LsSf https://astral.sh/uv/install.sh | sh
uv tool install zenit

direnv:

# Ubuntu/Debian
sudo apt install direnv

# Arch
sudo pacman -S direnv

# Fedora
sudo dnf install direnv

# Add to ~/.bashrc or ~/.zshrc:
eval "$(direnv hook bash)"   # or zsh

NixOS

NixOS does not allow uv to download or manage its own Python binaries — UV_PYTHON_DOWNLOADS must be set to never and uv must use the system Python. zenit handles this for you when run via the Nix flake, but you need Python 3.14+ available in your environment first.

Option A — run directly from the flake (recommended):

nix run github:BojanKonjevic/zenit -- my-project

This uses the bundled flake which sets UV_PYTHON_DOWNLOADS=never and points uv at the Nix-provided Python 3.14 automatically.

Option B — install and run manually:

# Make sure python3.14 is in your PATH (via nix-shell, home-manager, etc.)
# Then:
UV_PYTHON_DOWNLOADS=never uv tool install zenit
UV_PYTHON_DOWNLOADS=never zenit my-project

Generated projects on NixOS:

When zenit detects it's running on NixOS (by checking /etc/NIXOS), it generates a .envrc that uses use nix shell.nix instead of uv's own environment management, and writes a shell.nix that provides the correct LD_LIBRARY_PATH for compiled wheels like greenlet. So the generated project will also work correctly on NixOS.

direnv on NixOS — add to your configuration.nix:

programs.direnv.enable = true;

Or install it in your user environment:

nix-env -iA nixpkgs.direnv
# Add to ~/.bashrc or ~/.zshrc:
eval "$(direnv hook bash)"

Windows

Windows is supported. The generated justfile uses cmd as the shell, and all just recipes work without WSL.

# Install uv
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

# Install zenit
uv tool install zenit

direnv on Windows — direnv does not have an official Windows build. Your options:

  1. Skip it — instead of auto-activation, run uv sync once after scaffolding, then uv run <command> (or just use just, which routes through uv run already).
  2. Use WSL2 — install direnv inside WSL2 as per the Linux instructions above.
  3. Use scoop — a community-maintained build exists: scoop install direnv (not officially supported).

zenit will copy .envrc regardless, but will warn you if direnv is not found rather than failing.


Usage

zenit <project-name>            scaffold a new project
zenit <project-name> --dry-run  preview what would be created (nothing is written)
zenit add [addon]               add an addon to the current project
zenit add [addon] --dry-run     preview what the addon would change
zenit remove [addon]            remove an addon from the current project
zenit remove [addon] --dry-run  preview what would be removed
zenit doctor                    check project health against zenit's expectations
zenit config                    show config file path and current settings
zenit list-templates            show available templates
zenit list-addons               show available addons
zenit --version

All interactive prompts use arrow keys and space to select. If stdin is not a tty (e.g. piped input or CI), they fall back to numbered selection.

All commands that modify the filesystem support --dry-run — nothing is written, everything that would happen is printed.


Templates

blank

A minimal Python package with dev tooling. Good for CLIs, scripts, and libraries.

my-project/
  src/my_project/
    __init__.py
    main.py
    __main__.py
  tests/
    test_main.py
  pyproject.toml
  justfile
  .gitignore
  .gitattributes
  .pre-commit-config.yaml
  .envrc
  .env

Includes: pytest, ruff, mypy, pytest-cov, ipython.

fastapi

A production-oriented FastAPI setup. Requires the docker addon (selected automatically).

my-project/
  src/my_project/
    main.py           FastAPI app + lifespan
    lifecycle.py      startup/shutdown logic
    settings.py       pydantic-settings (reads from .env)
    exceptions.py     HTTP exception helpers
    api/
      router.py
      routes/
        health.py     GET /health → {"status": "ok"}
    core/
      security.py     JWT encode/decode, bcrypt hashing
    db/
      base.py         SQLAlchemy DeclarativeBase
      session.py      async engine, session factory, get_session dep
    models/
      mixins.py       TimestampMixin (created_at, updated_at)
    schemas/
      common.py       PaginationParams, PaginatedResponse
  alembic/
    env.py            async-compatible Alembic env
    versions/
  tests/
    conftest.py       session + anon_client + client fixtures
    integration/
      test_health.py
    unit/
    fixtures/
  scripts/
    wait_db.py        waits for postgres container to be ready
  .env                auto-generated with a random SECRET_KEY
  .env.example
  alembic.ini

Addons

Addons are selected interactively at scaffold time and can also be added or removed later with zenit add and zenit remove. They can be mixed freely with any template, with a few constraints noted below.

docker

Generates a multi-stage Dockerfile (uv-based, minimal final image), compose.yml, and .dockerignore.

  • For blank: compose starts just the app container.
  • For fastapi: compose also starts a db (postgres:16) service.
  • Required by fastapi (auto-selected).

redis

Adds src/<pkg>/integrations/redis.py — an async connection pool with a get_redis FastAPI dependency and a close_redis shutdown helper.

  • If docker is also selected: appends a redis:7-alpine service to compose.yml.
  • If docker is not selected: writes a standalone compose.redis.yml.
  • For fastapi: also patches settings.py to add a redis_url field.
  • Required by celery.

celery

Adds src/<pkg>/tasks/celery_app.py (Celery app configured against Redis) and tasks/example_tasks.py with a trivial add task.

  • Requires redis.
  • If docker is selected: appends celery-worker and celery-beat services to compose.yml.

sentry

Adds src/<pkg>/integrations/sentry.py with an init_sentry() function that no-ops when SENTRY_DSN is unset — safe to commit and run locally without a DSN.

  • For fastapi: patches lifecycle.py to call init_sentry() on startup, and patches settings.py and .env to add SENTRY_DSN / SENTRY_ENVIRONMENT.
  • For blank: patches main.py to call init_sentry() at startup.

github-actions

Writes .github/workflows/ci.yml that runs lint (ruff check), format check (ruff format --check), type check (mypy), and tests (pytest) on push and pull requests.

  • Spins up a postgres:16 service automatically when the fastapi template is used.
  • Spins up a redis:7-alpine service automatically when redis is selected.
  • Runs migrations before tests when postgres is present.

Adding and removing addons

After a project is scaffolded, you can add or remove addons without re-scaffolding.

cd my-project

# Interactive picker (shows what's installed, what's available, what's blocked)
zenit add
zenit remove

# Direct, non-interactive
zenit add redis
zenit remove sentry

# Preview first
zenit add celery --dry-run
zenit remove docker --dry-run

zenit add will refuse to apply an addon if:

  • It's already installed.
  • Its required addons aren't installed yet.
  • The project layout doesn't match what the addon expects (e.g. no src/ directory).

zenit remove will refuse to remove an addon if:

  • It's not installed.
  • Another installed addon depends on it.
  • The template requires it (e.g. docker on a fastapi project).

Both commands update .zenit.toml, pyproject.toml, justfile, compose.yml, and .env/.env.example as needed. They do not run uv sync — do that yourself after.


Health checks

cd my-project
zenit doctor

Runs a series of checks against the current project:

  • Metadata.zenit.toml is present, valid, and lists known addons with satisfied dependencies. Warns on version skew between the installed zenit and the one that scaffolded the project.
  • Dependenciespyproject.toml contains all runtime and dev deps expected by the template and installed addons.
  • Generated files — all files that should have been created by the template and addons are still present.
  • Extension points — sentinel markers used for addon injection are intact in non-Python files.
  • Addon integrity — each installed addon runs its own health check (e.g. verifying init_sentry() is called in lifecycle.py, checking REDIS_URL is set in .env).
  • Composecompose.yml is valid YAML, has no duplicate service definitions, and contains all expected services.
  • Environment variables — all expected keys are present in both .env and .env.example.

Exits with code 1 if any errors are found.


Configuration

zenit reads an optional config file for personal defaults:

Platform Path
Linux / macOS ~/.config/zenit/zenit.toml (or $XDG_CONFIG_HOME/zenit/zenit.toml)
Windows %APPDATA%\zenit\zenit.toml
default_template = "fastapi"
default_addons = ["docker", "github-actions"]

Run zenit config to see the resolved path and current settings.

Defaults are applied as pre-selections in the interactive prompt — you can still change them before confirming. The tool always works without a config file.


Generated commands (just)

All generated projects come with a justfile. Run just with no arguments to list what's available.

Command What it does
just test run pytest
just cov pytest with coverage report
just lint ruff check
just fmt ruff format
just fix ruff check --fix + ruff format
just check mypy
just run start the app (uvicorn --reload for fastapi, python -m for blank)

FastAPI only:

Command What it does
just migrate "message" generate an Alembic migration
just upgrade apply all pending migrations
just downgrade roll back one step
just db-create start db container, create app + test databases, migrate
just db-reset drop and recreate both databases
just wait-db wait until postgres is accepting connections

Docker addon:

Command What it does
just docker-up docker compose up --build
just docker-down docker compose down

Redis addon:

Command What it does
just redis-up start redis
just redis-down stop redis
just redis-cli open redis-cli

Celery addon:

Command What it does
just celery-up start worker + beat via compose
just celery-down stop worker + beat
just celery-flower open Flower monitoring UI on port 5555
just celery-logs tail worker logs

Sentry addon:

Command What it does
just sentry-check print sentry-sdk version
just sentry-test print whether SENTRY_DSN is set

Dry run

Pass --dry-run to any command that modifies the filesystem to preview everything without touching disk:

zenit my-project --dry-run
zenit add redis --dry-run
zenit remove sentry --dry-run

Output shows every file that would be created, modified, or deleted — every dependency change, every just recipe, every git command — then exits without writing anything.


Adding auth to a FastAPI project

The fastapi template lays out the security plumbing but stops short of generating auth routes (everyone's auth is different). When you're ready:

  1. Define User and RefreshToken models in models/ — import them in models/__init__.py so Alembic discovers them.
  2. Add core/dependencies.py with a get_current_user dependency using decode_access_token from core/security.py.
  3. Add api/routes/auth.py with register, login, and refresh endpoints.
  4. Register it in api/router.py.
  5. Uncomment the auth block in tests/conftest.py to enable the authenticated client fixture.
  6. Run just db-create && just migrate "add users" && just upgrade.

How zenit tracks your project

Every scaffolded project gets a .zenit.toml at the root:

[project]
template = "fastapi"
addons = ["docker", "redis", "sentry"]
zenit_version = "1.0.6"

This is the source of truth for zenit add, zenit remove, and zenit doctor. It is safe to commit. Do not edit it manually unless you know what you're doing — use zenit add and zenit remove instead.


Running from source

git clone https://github.com/BojanKonjevic/zenit.git
cd zenit
uv sync
uv run python main.py my-project

On NixOS, enter the dev shell first:

nix develop
python main.py my-project

License

MIT — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

zenit-1.0.7.tar.gz (109.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

zenit-1.0.7-py3-none-any.whl (107.4 kB view details)

Uploaded Python 3

File details

Details for the file zenit-1.0.7.tar.gz.

File metadata

  • Download URL: zenit-1.0.7.tar.gz
  • Upload date:
  • Size: 109.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"NixOS","version":"26.05","id":"yarara","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for zenit-1.0.7.tar.gz
Algorithm Hash digest
SHA256 65cfe335864bc7ea0b1d81277a9b2e4d3b015f5a47b83e026381fbc57b2110b4
MD5 c5f60de91e793d60558e7a3929a756b3
BLAKE2b-256 9b142f262f20b18eb47814fdf7988c81587a6992051341fd59599fc2f27e423f

See more details on using hashes here.

File details

Details for the file zenit-1.0.7-py3-none-any.whl.

File metadata

  • Download URL: zenit-1.0.7-py3-none-any.whl
  • Upload date:
  • Size: 107.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"NixOS","version":"26.05","id":"yarara","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for zenit-1.0.7-py3-none-any.whl
Algorithm Hash digest
SHA256 906c504d7353d49164c20ad5d7094703027b52ffe418648ac4f77d24d2e4b0a6
MD5 8cccb987ff49c4eb55c306116773cd41
BLAKE2b-256 00dc89e3f48bb4efafb8c44cd0d54dc9bd26fc6f20f0cf907d9a488947d14cfd

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page