# ADR-008: Packaging and Distribution

## Status

Accepted **Date:** 2026-02-14

## Context

cosalette is a reusable Python framework consumed by 8+ independent IoT-to-MQTT bridge
projects, each in its own repository. The framework needs a packaging and distribution
strategy that supports clean dependency management across multiple repos, works in CI/CD
pipelines (GitHub Actions) and Docker builds, and follows modern Python packaging
standards.

The project is open source (MIT license). The author wants to learn the PyPI publication
workflow and is willing to maintain the package, though the primary audience is personal
projects.

## Decision

Use **PyPI publication** with the package name `cosalette`, **hatchling** as the build
backend, **setuptools-scm** for version management, **src layout**, and a **`py.typed`
marker** (PEP 561) for type stub support, because this is the cleanest, most standard
distribution mechanism for Python packages.

### Package structure

```text
cosalette/
├── pyproject.toml
├── src/
│   └── cosalette/
│       ├── __init__.py
│       ├── py.typed          # PEP 561 marker
│       ├── _version.py       # Generated by setuptools-scm
│       └── ...
```

### Build configuration

```toml
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.build.targets.wheel]
packages = ["src/cosalette"]

[tool.setuptools_scm]
version_scheme = "guess-next-dev"
write_to = "src/cosalette/_version.py"
fallback_version = "0.0.0"
```

### Versioning strategy

- cosalette uses independent semver versioning
- Consumer projects pin with compatible ranges: `cosalette>=0.5,<1.0`
- Deprecation cycles can be short (one minor version) since all consumers are
  maintained by the same author

### Multi-repo layout

```text
github.com/ff-fab/
├── cosalette/           # Framework repository
├── velux2mqtt/          # Depends on cosalette
├── gas2mqtt/            # Depends on cosalette
├── wiz2mqtt/            # Depends on cosalette
└── ...                  # 5 more project repos
```

## Decision Drivers

- `pip install cosalette` must work everywhere (CI, Docker, devcontainers)
- Proper version resolution (pip/uv can resolve conflicts)
- Independent semver for framework and projects
- PEP 561 type stub support for type-checker compatibility
- Modern Python packaging standards (PEP 517/518/621)
- Professional practice and learning opportunity for PyPI publishing

## Considered Options

### Option 1: Poetry

Use Poetry for project management and build backend.

- *Advantages:* All-in-one tool (dependency management + build + publish). Lock file
  for reproducible installs. Large community.
- *Disadvantages:* Poetry's custom dependency resolver can conflict with pip/uv.
  Non-standard lock file format. The project already uses hatchling — switching adds
  no value. Poetry's `pyproject.toml` extensions are not PEP 621 compliant.

### Option 2: Flit

Use Flit for simple, minimal packaging.

- *Advantages:* Extremely simple for pure-Python packages. Minimal configuration.
- *Disadvantages:* Does not support dynamic version management (setuptools-scm).
  Limited build customisation. Less flexibility for future needs (C extensions,
  data files).

### Option 3: setuptools (legacy)

Use traditional setuptools with `setup.py` or `setup.cfg`.

- *Advantages:* Most widely used build backend. Maximum compatibility.
- *Disadvantages:* `setup.py` is legacy; `setup.cfg` is being superseded by
  `pyproject.toml`. Verbose configuration compared to hatchling. Does not support
  PEP 621 natively in older versions.

### Option 4: hatchling with PyPI publication (chosen)

Use hatchling as the PEP 517 build backend with PyPI as the distribution channel.

- *Advantages:* Modern PEP 517/518/621 compliant. Clean `pyproject.toml` configuration.
  Works with setuptools-scm for git-tag-based versioning. `pip install cosalette`
  works everywhere without git access. Hashes and integrity checks. Professional
  practice even for personal projects.
- *Disadvantages:* Requires PyPI account setup and release automation. Public package
  on PyPI (acceptable for open source project).

## Decision Matrix

| Criterion          | Poetry | Flit | setuptools (Legacy) | hatchling + PyPI |
| ------------------ | ------ | ---- | ------------------- | ---------------- |
| PEP compliance     | 3      | 4    | 3                   | 5                |
| Version management | 4      | 2    | 3                   | 5                |
| Install simplicity | 4      | 5    | 3                   | 5                |
| Build flexibility  | 3      | 2    | 5                   | 4                |
| Community adoption | 5      | 3    | 4                   | 4                |

_Scale: 1 (poor) to 5 (excellent)_

## Consequences

### Positive

- `pip install cosalette` works in any environment — CI, Docker, devcontainers,
  bare metal
- Version resolution by pip/uv handles dependency conflicts automatically
- `py.typed` marker enables type-checking consumers to verify against cosalette's
  type annotations
- setuptools-scm eliminates manual version management — git tags drive version numbers
- src layout prevents accidental imports of the development source

### Negative

- PyPI publication requires release automation (GitHub Actions workflow) and account
  management
- The package is public on PyPI — though this is acceptable for an open-source project
- setuptools-scm requires git tags to be managed carefully — missing tags produce
  dev versions

_2026-02-14_
