Typorithm · medium

pyproject.toml: build metadata versus runtime requirements

pythonlibrariespackagingindependent-practice
Saved on this device
Full lesson · 4 learning steps Open any exercise for focused practice
1

Step 1 of 4 · intro card

pyproject.toml: build metadata versus runtime requirements

0%

Concept and contract

Read project metadata without confusing build tools, runtime dependencies and optional extras.

A pyproject.toml file can describe several distinct concerns. Build-system requirements supply the backend environment; project dependencies describe what the installed distribution needs at runtime; optional dependency groups describe extras a consumer may choose. Treating those lists as interchangeable can produce bloated or broken packages.

The example parses an owned TOML document with the standard-library tomllib module. Parsing is not complete packaging validation: the code observes selected fields and expects this fixture's required keys. A real backend and installer later interpret the metadata according to their own responsibilities and the packaging specifications.

Mechanism and worked example

The worked report returns the project name, version, runtime dependency list, build requirement list and requires-python string.

The worked report returns the project name, version, runtime dependency list, build requirement list and requires-python string. The fixture needs setuptools to build but has no runtime dependencies. Its test extra names mypy without making mypy a requirement for every installed user.

The cases vary the version and remove the optional test group while keeping the core observation stable. They make the distinction visible as actual parsed data. TOML key names and package metadata are not Python imports: a distribution name with a hyphen need not be the same string a user imports in source code.

Complete worked example

python

import tomllib

def inspect_example(text):
    document = tomllib.loads(text)
    project = document['project']
    build = document['build-system']
    return {'name': project['name'], 'version': project['version'],
            'runtime': project.get('dependencies', []),
            'build': build['requires'], 'python': project['requires-python']}
Reasoning
The worked report returns the project name, version, runtime dependency list, build requirement list and requires-python string. The fixture needs setuptools to build but has no runtime dependencies. Its test extra names mypy without making mypy a requirement for every installed user.
Download the reusable Python and packaging computer lab ↗

Focused decision: use the complete example for context

text

[build-system]
requires = ["setuptools==84.2.1"]
build-backend = "setuptools.build_meta"
Decision
Keep build requirements separate from runtime dependencies and report the optional dependency group names.

Counterexamples and common mistakes

The controlled bug reports build requirements as runtime dependencies.

The controlled bug reports build requirements as runtime dependencies. The resulting dictionary may look plausible because both are lists of requirement strings, but it falsely tells consumers that a build tool belongs in the installed library's runtime contract.

Another common mistake assumes requires-python proves compatibility with every interpreter satisfying the expression. That field declares an intended version constraint; testing on supported interpreters supplies separate evidence. Likewise, reading a version string is different from validating it or confirming that the built wheel actually contains matching version metadata.

Controlled bug: the executable contract rejects this version

python

import tomllib

def inspect_example(text):
    document = tomllib.loads(text)
    project = document['project']
    build = document['build-system']
    return {'name': project['name'], 'version': project['version'],
            'runtime': build['requires'],
            'build': build['requires'], 'python': project['requires-python']}
Reasoning
The controlled bug reports build requirements as runtime dependencies. The resulting dictionary may look plausible because both are lists of requirement strings, but it falsely tells consumers that a build tool belongs in the installed library's runtime contract.
Download the reusable Python and packaging computer lab ↗

Independent practice

Add extras to the report as sorted optional-dependency group names.

Add extras to the report as sorted optional-dependency group names. Preserve the existing distinction between runtime and build requirements. The first hint is to inspect optional-dependencies inside project, rather than scanning the top-level build-system table.

The second hint is to use an empty mapping when the optional table is absent. The fixture with no test group should yield an empty list instead of a missing-key failure. Return group names, not every requirement in those groups, because the new acceptance contract asks which optional interfaces the project exposes.

Step 1: Keep build requirements separate from runtime dependencies and report the optional dependency group names. Step 2: Run the accepted baseline, edit the exercise, then check its independent solution cases; preserve caller inputs and the documented exception behavior.

Practice starter: solve before reading the solution

python

import tomllib

def inspect_example(text):
    document = tomllib.loads(text)
    project = document['project']
    build = document['build-system']
    return {'name': project['name'], 'version': project['version'],
            'runtime': project.get('dependencies', []),
            'build': build['requires'], 'python': project['requires-python']}

# Task: Keep build requirements separate from runtime dependencies and report the optional dependency group names.
# Practice contract: Keep build requirements separate from runtime dependencies and report the optional dependency group names.
Reasoning
Keep build requirements separate from runtime dependencies and report the optional dependency group names.
Download the reusable Python and packaging computer lab ↗
  • Hint 1: Add extras to the report as sorted optional-dependency group names. Preserve the existing distinction between runtime and build requirements. The first hint is to inspect optional-dependencies inside project, rather than scanning the top-level build-system table.
  • Hint 2: The second hint is to use an empty mapping when the optional table is absent. The fixture with no test group should yield an empty list instead of a missing-key failure. Return group names, not every requirement in those groups, because the new acceptance contract asks which optional interfaces the project exposes.

Solution, tests and trade-offs

The solution reads the optional mapping with an empty default and sorts its keys.

The solution reads the optional mapping with an empty default and sorts its keys. Acceptance checks both a present test group and an absent group, alongside the independent project version cases. The exercise therefore protects useful missing-field behavior without pretending all missing metadata should silently default.

The companion packaging lessons build a real source archive and wheel from a src-layout project. That later evidence is stronger than merely parsing pyproject.toml. Keep build-tool pins in the owned lab environment, describe runtime dependencies honestly and validate the installed artifact before claiming that a package is ready for another application to consume.

Practice solution and acceptance checks

python

import tomllib

def inspect_example(text):
    document = tomllib.loads(text)
    project = document['project']
    build = document['build-system']
    return {'name': project['name'], 'version': project['version'],
            'runtime': project.get('dependencies', []),
            'build': build['requires'], 'python': project['requires-python'], 'extras': sorted(project.get('optional-dependencies', {}))}
Reasoning
The solution reads the optional mapping with an empty default and sorts its keys. Acceptance checks both a present test group and an absent group, alongside the independent project version cases. The exercise therefore protects useful missing-field behavior without pretending all missing metadata should silently default.
Download the reusable Python and packaging computer lab ↗

Interview reasoning and next step

Explain why an end user installing a wheel may not need the build backend listed here.

Explain why an end user installing a wheel may not need the build backend listed here. Then distinguish runtime dependencies, optional extras and the lab's pinned tooling. Name the evidence needed beyond successful TOML parsing before calling the distribution usable.

For a stretch, add a genuine optional extra to an owned project and inspect the resulting wheel metadata after building. Compare its declared dependencies with the raw configuration. Do not upload the teaching distribution to a package index; local artifacts are sufficient to verify the metadata relationships and installation behavior.

Optional stretch — after the core task works: For a stretch, add a genuine optional extra to an owned project and inspect the resulting wheel metadata after building. Compare its declared dependencies with the raw configuration. Do not upload the teaching distribution to a package index; local artifacts are sufficient to verify the metadata relationships and installation behavior.

Computer practice: https://applydjinn.com/typorithm/python-engineering-lab-20261107.zip. Use an isolated Python 3.13 environment, install requirements.txt, then run verify.py. Edit exercises/pye-metadata.py and run --practice pye-metadata. Fixture files are created in fresh temporary directories for every case. Packaging checks build real local artifacts, install the exact wheel without an index and run outside the source tree. No package is published to PyPI. The app includes static examples, prepared results and questions; it does not run Python or provide an editor.

Read project metadata without confusing build tools, runtime dependencies and optional extras.