---
title: "Packaging Code"
subtitle: "November 30 · Give the receiver an installable command"
author: "MaDS Databases & SQL"
format:
  revealjs:
    theme: [default, mads-sql-reveal.scss]
    slide-number: c/t
    chalkboard: true
    code-line-numbers: true
    transition: fade
    footer: "Adapted from Alex Reinhart · MADS Computing"
---

## One question will organize today

::: {.question}
Can a clean environment install and run the inherited workflow without changing `sys.path` or guessing which file starts it?
:::

## Course source and adaptation

::: {.source-note}
Today's sequence follows Alex Reinhart's [Packaging Code](https://www.refsmmat.com/courses/msp-computing/large-scale-data/packaging.html), updated to a `pyproject.toml` and `src/` layout following current Python Packaging Authority guidance.

Current reference: [PyPA packaging projects](https://packaging.python.org/en/latest/tutorials/packaging-projects/) and [command-line tools](https://packaging.python.org/en/latest/guides/creating-command-line-tools/).
:::

## By the end of class

You should be able to:

- separate reusable modules from entry points,
- explain imports without `sys.path` tricks,
- create a minimal `pyproject.toml`,
- expose, build, and install a command,
- test the artifact in a clean environment.

## A folder of scripts has hidden contracts {.smaller}

Questions a receiver should not have to ask:

- Which file runs first?
- From which directory?
- Which dependencies and versions?
- Which settings are required?
- What is safe to import?
- What output proves success?

Packaging makes these contracts explicit.

## Modules are importable units

```python
# src/client_pipeline/validation.py
def require_nonempty(rows: list[tuple]) -> None:
    if not rows:
        raise ValueError("No rows received")
```

Keep useful logic out of top-level side effects.

## Guard direct execution

```python
def main() -> int:
    ...
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
```

Importing the module should not refresh production data.

## Packages organize related modules

```text
src/
└── client_pipeline/
    ├── __init__.py
    ├── cli.py
    ├── config.py
    ├── db.py
    ├── ingest.py
    └── validation.py
```

Package boundaries should reflect responsibilities.

## Checkpoint 1 · Untangle one script

::: {.checkpoint}
Take one inherited script and mark configuration, I/O, transformation, validation, and CLI behavior. Propose two or three modules and one entry point.
:::

## Use imports, not working-directory luck

Inside the package:

```python
from .config import Settings
from .db import connect
from .validation import require_nonempty
```

Do not append project paths to `sys.path` in production code.

## `pyproject.toml` names the build contract

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

[project]
name = "client-pipeline"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = ["psycopg[binary]>=3.2,<4"]
```

Commit dependency metadata—not a virtual environment.

## Expose a command

```toml
[project.scripts]
client-pipeline = "client_pipeline.cli:main"
```

After installation:

```bash
client-pipeline --help
```

The command is the receiver's stable entry point.

## Configuration stays outside the package

```text
package: validates setting names and types
environment: supplies deployment-specific values
runbook: documents how the receiver obtains them
```

Do not package `.env`, credentials, raw client data, or local caches.

## Checkpoint 2 · Write the minimal metadata

::: {.checkpoint}
Create a project name, supported Python version, runtime dependencies, and CLI entry point for the inherited workflow.
:::

Run the metadata through one teammate: can they identify the command and configuration without opening the source?

## Build artifacts are installable evidence

```bash
python -m build
```

Produces a source distribution and wheel in `dist/`.

The wheel is the installable artifact; the repository remains the collaboration source.

## Clean-environment test

```bash
python -m venv .venv-test
source .venv-test/bin/activate
python -m pip install dist/*.whl
client-pipeline --help
```

Then run the smallest-success command with test-safe configuration.

## Editable installs are for development

```bash
python -m pip install -e .
```

Useful while editing, but not proof that the built wheel contains everything required.

## Tests should import the installed package

At minimum:

- configuration validation,
- pure transformation/validation function,
- CLI `--help` and invalid input,
- smallest database query against a test schema,
- clean failure with missing `DATABASE_URL`.

## Checkpoint 3 · Break the clean install

::: {.checkpoint}
Build and install the inherited package in a clean environment. Record the first failure, classify its hidden dependency, fix it, and repeat.
:::

## Version behavior, not filenames

A receiver needs to know:

- artifact version,
- schema compatibility,
- source/API assumptions,
- behavior changes,
- rollback artifact.

`final_v2_really_final.py` is not release management.

## Project transfer

::: {.project-prompt}
Package one inherited refresh or report path and add the wheel-install test to the receiver evidence pack.
:::

Small and reliable beats packaging every experiment.

## Homework starts here

Homework 3 may use packaging as its tested extension. Link to `pyproject.toml`, source
layout, built artifact, clean-install log, and smallest-success output.

## The pattern to keep

```text
separate reusable logic
  → define the package boundary
  → declare build and dependencies
  → expose one stable command
  → build a wheel
  → install it clean
  → test smallest success and failure
```

Next: run the receiver acceptance test and shape the final client readout.
