Pyproject Development Guide

This is a brief overview of the Python packaging specification, in addition to some narrowed conventions for Python development in the pntOS-Python repository.

Project Configuration (pyproject.toml)

Each Python package is configured via pyproject.toml, which defines metadata, dependencies, and build settings.

Basic Package Metadata

[project]
name = "pntos-api"
version = "0.1.0.dev0"
description = "The API specification for pntOS"
readme = "README.md"
authors = [ { name = "IS4S", email = "pntos@is4s.com" } ]
requires-python = ">=3.10"

classifiers = [
    "Programming Language :: Python :: 3 :: Only",
    "Programming Language :: Python :: 3.10",
    "Programming Language :: Python :: 3.11",
    "Programming Language :: Python :: 3.12",
    "Programming Language :: Python :: 3.13",
]

See the Python packaging specification for all available fields.

Versioning

By convention, the version should adhere to semantic versioning. In short:

Given a version number MAJOR.MINOR.PATCH, increment the:

  1. MAJOR version when you make incompatible API changes

  2. MINOR version when you add functionality in a backward compatible manner

  3. PATCH version when you make backward compatible bug fixes

Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format.

Here are some concrete examples of when to increment each version number:

Version Type

Example Change

Why It Matters

Impact on Downstream Projects

MAJOR

Renaming a function from foo() to bar()

Breaking change - the old function name no longer exists

Projects using foo() will break and need to update their code to use bar()

MAJOR

Changing a function signature from process(data) to process(data, format) where format is required

Breaking change - existing calls are no longer valid

Projects must update all process() calls to include the new required parameter

MINOR

Adding a new function baz() to the API

New functionality added

Projects can choose to use the new baz() function, but existing code continues to work without changes

MINOR

Adding an optional parameter: process(data, format="json")

New functionality that’s backward compatible

Projects can use the new format parameter if desired, but existing process(data) calls still work

PATCH

Fixing a bug where calculate_sum([1, 2, 3]) incorrectly returned 5 instead of 6

Bug fix with no API changes

Projects get the correct behavior automatically; no code changes needed

PATCH

Improving performance of search() without changing its behavior

Internal improvement with no API changes

Projects benefit from better performance; no code changes needed

For pre-release versions, the convention is to append .devN where N corresponds to the pre-release version number (e.g. 0.1.0.dev0).

Note

These conventions are a subset of the full Python versioning specification.

Dependencies

By convention, dependencies are categorized into two types:

Type

Purpose

Location In pyproject.toml

Example Use Case

Core

Required to use the package

[project]
dependencies = [...]

Package function foo() uses numpy for a calculation - downstream users cannot use foo() without numpy.

Dev

Only needed for development

[dependency-groups]
dev = [...]

Package uses pytest for CI/CD tests - downstream users do not need pytest to use package.

Example:

[project]
dependencies = [
    "numpy",  # Core: needed by package users
]

[dependency-groups]
dev = [
    "pytest",      # Dev: only for testing
    "ruff",        # Dev: only for linting
]

The simplest dependency specification is just the package name. Version constraints and other options are covered in the next section.

Dependency Specifiers

Common patterns for specifying dependencies (see Python dependency specifiers for full details):

Type

Syntax

Example

Use Case

Version constraints

==, >=, <=, >, <, ~=, !=

"numpy==1.24.0"
"pandas>=2.0.0"
"matplotlib>=3.5,<4.0"
"scipy~=1.10.0"

Exact version
Minimum version
Version range
Compatible release (≥1.10.0, <1.11.0)

Git dependencies

@ git+https://...
@ git+ssh://...

"pkg @ git+https://github.com/user/repo.git"
"pkg @ ...repo.git@main"
"pkg @ ...repo.git@v1.0.0"
"pkg @ ...repo.git@abc123"
"pkg @ ...repo.git@abc123#subdirectory=path/to/pkg"

Latest from default branch
Specific branch
Specific tag
Specific commit
Subdirectory in monorepo

Local paths

@ file://...

"pkg @ file:///absolute/path"
"pkg @ file://./relative/path"

Absolute path
Relative path

Next Steps