Skip to main content

Development workflow toolkit for Python projects: pyproject.toml sync, doit tasks, nox sessions

Project description

PyProjKit

Development workflow toolkit for Python projects

Python versions PyPI Tests Coverage Code style: black

Motivation

Every Python project accumulates the same development workflow boilerplate: formatter settings in pyproject.toml, supported Python versions repeated across classifiers, requires-python, black's target-version, and the testing matrix, plus a dodo.py and noxfile.py that look nearly identical from project to project. Keeping all of it consistent — and keeping it up to date as tools and Python versions evolve — is tedious and error-prone.

PyProjKit centralizes all of this. Each project declares its configuration exactly once in a pyprojconf.py at the project root; PyProjKit then:

  1. Syncs pyproject.toml: writes the managed parts (version classifiers, requires-python, [tool.*] tables) while preserving everything else, with a --check mode for CI.
  2. Provides doit task factories: format, test, badges, publish, and more, pre-wired to sensible conventions.
  3. Provides nox session factories: a test session across all supported Python versions, pinned to exact patch releases.

Supported Python versions are declared once and drive everything: classifiers, requires-python, black's target-version, mypy's python_version, and the nox interpreter matrix.

Getting started

Install from PyPI:

pip install pyprojkit

Or add it to your project's dev dependencies (formatter, test, and analysis tools are available as extras):

[dependency-groups]
dev = [
  "pyprojkit[all]"
]

Then drop three small files into your project root.

pyprojconf.py:

from pyprojkit import ProjectConfig, PythonVersions

config = ProjectConfig(package="my_package", python=PythonVersions(3, (12, 14)))

dodo.py:

from pyprojkit import TaskFactory

globals().update(TaskFactory().create_all_tasks())

noxfile.py:

from pyprojkit import NoxFactory

test = NoxFactory().create_test_session()

Then sync your pyproject.toml and run your workflow:

doit sync    # or: pyprojkit sync
doit format
doit test
doit badges
nox

Declaring configuration: pyprojconf.py

pyprojconf.py must define a module-level config: ProjectConfig. TaskFactory, NoxFactory, and the pyprojkit CLI load it automatically from the current directory.

Python versions

PythonVersions takes the major version and an inclusive range of minor versions — only versions you explicitly support (i.e. test against):

python = PythonVersions(3, (12, 14))

This single declaration drives:

  • requires-python (>=3.12,<3.15)
  • Trove classifiers (Programming Language :: Python :: 3.12, ...)
  • black's target-version (["py312", "py313", "py314"])
  • mypy's python_version (3.12)
  • nox interpreter pins (3.12.13, 3.13.13, 3.14.6)

The exact patch releases used for nox come from a mapping bundled with PyProjKit, updated with each release — so updating PyProjKit refreshes the pins across all your projects. Individual projects can override:

python = PythonVersions(3, (12, 14), patch_overrides={(3, 14): "3.14.7"})

Tools and profiles

Tool configurations are grouped by category under ToolsConfig, whose defaults constitute the default profile:

  • formatting: ordered chain of formatters; by default autoflake → isort → black → docformatter → toml-sort
  • test: pytest + coverage settings
  • doit, nox: cache locations and backends
  • doc: documentation tools — mkinit (enabled by default) and sphinx (opt-in)
  • analysis: mypy + pyright (opt-in)
  • publish: build/publish output directory

Customize with dataclasses.replace:

from dataclasses import replace

from pyprojkit import (
    AnalysisConfig,
    DocConfig,
    ProjectConfig,
    PythonVersions,
    SphinxConfig,
    ToolsConfig,
)

config = ProjectConfig(
    package="my_package",
    python=PythonVersions(3, (12, 14)),
    tools=replace(
        ToolsConfig.default(),
        doc=DocConfig(sphinx=SphinxConfig(copy_env_var="MY_PACKAGE_DOCS_DIR")),
        analysis=AnalysisConfig(),
    ),
)

For one-off tweaks to managed tables there's an escape hatch, merged last into the synced output:

tools=replace(
    ToolsConfig.default(),
    tool_overrides={"tool.pytest.ini_options": {"addopts": "-x"}},
)

Syncing pyproject.toml

Formatters and other tools read their settings from pyproject.toml, so PyProjKit writes them there — pyproject.toml remains the single source of truth for the tools themselves, while pyprojconf.py is the single source of truth for you.

Managed content

The sync engine owns:

  • project.requires-python
  • Python version classifiers (other classifiers are untouched)
  • One [tool.X] table per enabled tool (fully owned; hand edits inside are overwritten)
  • [tool.pyprojkit].managed: bookkeeping list of owned tables, so a tool dropped from your configuration gets its table cleanly removed on the next sync

Everything else — dependencies, [tool.uv.sources], build system, unmanaged tool tables — is preserved. Output is normalized with toml-sort using the same settings as the managed [tool.tomlsort] table, so syncing and formatting never fight.

Check mode

Verify a project is in sync without writing (e.g. in CI):

pyprojkit sync --check

Prints a diff and exits nonzero if pyproject.toml is out of date.

doit tasks

TaskFactory creates doit tasks pre-wired to the configuration. Create them selectively:

from pyprojkit import TaskFactory

factory = TaskFactory()

task_sync = factory.create_sync_task()
task_format = factory.create_format_task()
task_test = factory.create_test_task()
task_badges = factory.create_badges_task()
task_publish = factory.create_publish_task()

or all at once: globals().update(factory.create_all_tasks()), which creates every task enabled by the configuration.

Task Description
sync Update pyproject.toml from pyprojconf.py
format Run the configured formatter chain over the project
test Run pytest with coverage (HTML + XML) and JUnit output
badges Generate test/coverage badges via genbadge
publish Build and publish via uv (output dir is cleaned first, so stale artifacts are never published)
init Generate __init__.py files via mkinit (enabled by default)
doc Build documentation via sphinx; --copy deploys to a directory given by an env var (opt-in)
analysis Run mypy (with HTML/cobertura reports) and pyright (opt-in)

The format task passes an explicit list of paths to each formatter — root *.py/*.toml files plus the configured format_paths directories that exist (src, test, doc, examples by default) — so formatters never wander into .venv or other unrelated trees.

nox sessions

NoxFactory.create_test_session() registers a test session which, for each pinned interpreter, syncs the project's dev dependency group via uv into the session environment and runs pytest:

$ nox -l
* test-3.12.13
* test-3.13.13
* test-3.14.6

Extra arguments pass through to pytest: nox -s test-3.12.13 -- -k my_test.

Path conventions

All tasks share the same layout:

  • __cache__/: caches (doit db, pytest cache, coverage data, nox envs)
  • __out__/: generated artifacts (test/ coverage + JUnit results, doc/html, analysis/, uv/ build artifacts)
  • badges/: generated badge SVGs
  • src/<package>: package sources consumed by the init and analysis tasks; the containing directory is configurable via ProjectConfig.packages_dir (default "src")

Custom profiles

A profile is a named factory for a pre-canned configuration, at two levels: the overall tools profile and the formatting profile it contains. The "default" profiles ship with PyProjKit; register your own once and reuse it across projects:

# in your shared module, e.g. mycompany_profiles.py
from dataclasses import replace

from pyprojkit import AnalysisConfig, ToolsConfig, register_tools_profile

register_tools_profile(
    "mycompany",
    lambda: replace(ToolsConfig.default(), analysis=AnalysisConfig()),
)
# in each project's pyprojconf.py
import mycompany_profiles  # noqa: F401 -- registers the profile

from pyprojkit import ProjectConfig, PythonVersions, get_tools_profile

config = ProjectConfig(
    package="my_package",
    python=PythonVersions(3, (12, 14)),
    tools=get_tools_profile("mycompany"),
)

New formatters are equally pluggable: subclass BaseFormatterConfig (declaring the tool's [tool.X] table and command line) and include it in a FormattingConfig — the sync engine and format task pick it up automatically.

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

pyprojkit-0.2.0.tar.gz (18.2 kB view details)

Uploaded Source

Built Distribution

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

pyprojkit-0.2.0-py3-none-any.whl (24.4 kB view details)

Uploaded Python 3

File details

Details for the file pyprojkit-0.2.0.tar.gz.

File metadata

  • Download URL: pyprojkit-0.2.0.tar.gz
  • Upload date:
  • Size: 18.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.25 {"installer":{"name":"uv","version":"0.11.25","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for pyprojkit-0.2.0.tar.gz
Algorithm Hash digest
SHA256 25775d4294268c846e0d66cc27ac9840f655c50a83abaa8461ede28a33a4eda6
MD5 b7692857e1e3a5efbe6c651a65e2d9cf
BLAKE2b-256 59a7b5e8fd3ba54e2d980aaeb2a002b3d9ad2e6253c337e2ea0b69216f604291

See more details on using hashes here.

File details

Details for the file pyprojkit-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: pyprojkit-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 24.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.25 {"installer":{"name":"uv","version":"0.11.25","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for pyprojkit-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 70191f89d884eb08229c83da923c00e8089177d2f02d3d4ff858a7a24c428a8a
MD5 b145730f551035dabcb082ae4630cce2
BLAKE2b-256 7e9c333513b38b417d62d0be677c951ddcb92d3cb0928e0f49a442d83575d2bd

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