# Pyproject Development Guide
This is a brief overview of the [Python packaging
specification](https://packaging.python.org/en/latest/specifications/pyproject-toml/#pyproject-toml-spec),
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
```toml
[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](https://packaging.python.org/en/latest/specifications/pyproject-toml/#declaring-project-metadata-the-project-table) for all available fields.
#### Versioning
By convention, the version should adhere to [semantic versioning](https://semver.org/).
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](https://packaging.python.org/en/latest/specifications/version-specifiers/#version-specifiers).
```
### 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:
```toml
[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](https://packaging.python.org/en/latest/specifications/dependency-specifiers/#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
- [](./uv.md)
- [](./installation.md)
- [](./contributing.md)