Packages and Dependencies Checkpoint
25 min · Intermediate
Need help? Ask a tutor on WhatsApp (opens in a new tab)Fix the mistakes
The slips that cost marks in this module. Spot each one, then check the fix.
Everything in this module meets in one project: a package folder, a pyproject.toml, a set of pinned dependencies and a release. This checkpoint walks through them and then tests your choices.
Lay out and describe the project
A shippable project has a package folder with an __init__.py, and one pyproject.toml beside it. Ask of each file: who reads it, and what happens if it is missing?
Example 1 · Checking a project file
import tomllib
text = """[build-system]requires = ["setuptools>=68"]build-backend = "setuptools.build_meta"
[project]name = "shopkit"version = "0.1.0"requires-python = ">=3.10"dependencies = ["python-dateutil>=2.8"]"""
def problems(config): found = [] if "build-system" not in config: found.append("missing [build-system]") project = config.get("project", {}) for key in ("name", "version", "requires-python"): if key not in project: found.append(f"missing {key}") return found
print(problems(tomllib.loads(text)))print(problems(tomllib.loads("[project]\nname = \"x\"")))[] ['missing [build-system]', 'missing version', 'missing requires-python']
What this code does
- The function lists what is missing, so an empty list means the file has the basics.
- The second file has a name only, so it reports three gaps.
Without __init__.py the folder is not a regular package. Without [build-system], pip guesses how to build. Without name and version, there is nothing to install or publish.
Choose where each version rule lives
Ranges go in pyproject.toml, so the package fits with other people's code. Exact versions go in a lock file or requirements.txt, so your own application is repeatable.
Example 2 · Where rules live
ranges = ["python-dateutil>=2.8"]lock = {"python-dateutil": "4.0.0", "tzdata": "2026.6"}
for rule in ranges: name = rule.split(">=")[0] print(f"{name}: rule {rule}, locked at {lock[name]}")print("locked but not in the rules:", sorted(set(lock) - {r.split(">=")[0] for r in ranges}))python-dateutil: rule python-dateutil>=2.8, locked at 4.0.0 locked but not in the rules: ['tzdata']
What this code does
- The rule says what is allowed. The lock says what was tested.
tzdataappears only in the lock, because it is a transitive dependency.
Tools for development, such as mypy, go in an optional dev group. They should never be forced on the users of the package.
Check before you publish
Build, list, install and import. Each step catches a different mistake: a bad pyproject.toml, a stray file in the wheel, a missing module, or a version that was already used.
Example 3 · A release check
def ready(version, published, wheel_files): problems = [] if version in published: problems.append("version already published") if any(name.endswith(".env") for name in wheel_files): problems.append("secret file in wheel") return problems or ["ok"]
print(ready("0.2.0", ["0.1.0"], ["shopkit/__init__.py"]))print(ready("0.1.0", ["0.1.0"], ["shopkit/__init__.py", "shopkit/.env"]))['ok'] ['version already published', 'secret file in wheel']
What this code does
- Two real release mistakes are caught: a used version number and a secret in the package.
- On a real project, the file list comes from listing the wheel.
Use TestPyPI to practise. Keep upload tokens out of the code, and raise the version for every release.
Finish up
Mark this lesson complete
This is the last lesson in this section.