Dependency Management
Dependency classification affects security reviews, package size, deployment, and license obligations.
What this module covers
Direct, transitive, optional, and development dependencies Version ranges and semantic versioning
Lockfiles and repeatable dependency resolution Registries, repositories, mirrors, and proxies Vendoring and checked-in dependencies
System packages versus project-local dependencies Dependency conflicts and resolution strategies Private packages and credentials
Vulnerability scanning, license compliance, and SBOMs Dependency update policy and maintenance windows
7.1 Dependency types
Direct dependency: explicitly used by your project.
Transitive dependency: required by another dependency.
Development dependency: needed for tests, linting, code generation, or development only.
Optional dependency: used only for a feature or platform.
Build dependency: required to produce artifacts, even if absent at runtime.
Runtime dependency: required when the program executes.
Dependency classification affects security reviews, package size, deployment, and license obligations.
7.2 Semantic versioning
A common convention is MAJOR.MINOR.PATCH:
Major: incompatible public API changes.
Minor: backward-compatible functionality.
Patch: backward-compatible fixes.
This is a convention, not a guarantee of quality. Version-range syntax differs across package managers. A broad version range may select a newer version later; a lockfile can record the actual resolved graph.
How ranges resolve Output (CPython 3.13.1): A caret range today resolves to 1.9.1; when 1.10.0 is published it would silently resolve to that instead. A lockfile records the version that was actually resolved so every machine installs the same graph until someone updates it deliberately. This sketch covers plain MAJOR.MINOR.PATCH only; real package managers also handle pre-release tags and more operators, and each ecosystem's syntax differs.
def parse(v):
return tuple(int(x) for x in v.split("."))
def satisfies(version, rng):
v = parse(version)
if rng.startswith("^"): # same major (or same minor when major is 0)
b = parse(rng[1:])
upper = (b[0] + 1, 0, 0) if b[0] > 0 else (0, b[1] + 1, 0)
return b <= v < upper
if rng.startswith("~"): # same major.minor
b = parse(rng[1:])
return b <= v < (b[0], b[1] + 1, 0)
return v == parse(rng) # exact pin
published = ["1.2.0", "1.2.5", "1.3.0", "1.9.1", "2.0.0", "0.4.2", "0.4.9", "0.5.0"]
for rng in ["^1.2.0", "~1.2.0", "1.2.5", "^0.4.2"]:
matches = [v for v in published if satisfies(v, rng)]
best = max(matches, key=parse) if matches else None
print(f"{rng:<8} matches {matches} -> resolves to {best}")^1.2.0 matches ['1.2.0', '1.2.5', '1.3.0', '1.9.1'] -> resolves to 1.9.1
~1.2.0 matches ['1.2.0', '1.2.5'] -> resolves to 1.2.5
1.2.5 matches ['1.2.5'] -> resolves to 1.2.5
^0.4.2 matches ['0.4.2', '0.4.9'] -> resolves to 0.4.97.3 Lockfiles
Lockfiles record resolved package versions and often integrity hashes. They help teams and CI resolve the same dependency graph.
Good practices:
Commit the relevant lockfile for applications where the ecosystem recommends it.
Review lockfile changes alongside manifest changes.
Update dependencies deliberately and run tests/security scans.
Avoid manually editing generated lockfiles unless the tool explicitly supports it.
Pin package-manager/tool versions where repeatability matters.
7.4 Vendoring
Vendoring stores third-party source or package contents inside a repository. Benefits include offline builds and controlled inputs. Costs include repository growth, update burden, and possible license obligations. Maintain clear origin/version/license metadata.
7.5 Registries and private packages
Private registries can require tokens or certificates. Use secret stores or short-lived credentials in CI; do not write credentials into source files, lockfiles, container layers, or public logs.
7.6 Supply-chain checks
Useful checks include:
Dependency vulnerability scanning.
License policy scanning.
Signature and checksum verification.
Package source/registry allowlists.
Pinning trusted build tools.
Monitoring abandoned or compromised dependencies.
A scanner finding requires triage; a clean scan does not prove a dependency is safe.
Quiz
? `"^1.2.0"` with versions 1.2.5, 1.9.1 and 2.0.0 published — what is resolved?
- 1.2.0 :: Caret allows newer minor and patch versions.
- 2.0.0 :: A new major is excluded.
+ 1.9.1 :: The highest version below 2.0.0.
- 1.2.5 :: That is what `~1.2.0` would pick.
? What is the main job of a lockfile?
+ Record the exact resolved versions (and often integrity hashes) so installs are repeatable :: Ranges alone resolve differently over time.
- Prevent anyone from updating dependencies :: Updates are still possible, deliberately.
- Speed up compilation :: It affects resolution, not compilation.
- Replace the manifest :: The manifest still declares intent.
? Which are supply-chain checks? (select all that apply)
+ Signature and checksum verification :: Confirms the bytes are the expected ones.
+ Registry allowlists :: Limits where packages can come from.
+ License policy scanning :: Part of dependency governance.
- Deleting the lockfile before each build :: That removes repeatability.