Python — Enterprise Python Applications
Python Dependency Management at Scale
Direct answer
Python dependency management at scale comes down to four practices: a committed lockfile with hashes for every deployable, one standardized tool across the organization (uv is my current default), a written constraint policy — apps pin via lockfiles, internal libraries declare wide ranges — and upgrades treated as a weekly automated routine gated by CI and vulnerability audits rather than a yearly crisis. An internal package index for shared code and supply-chain insulation rounds it out.
Dependency chaos is the quietest way large Python organizations lose velocity: every service resolves packages differently, upgrades are feared, and a single CVE turns into a two-week inventory hunt. The fixes are mostly policy, applied consistently — here is what I standardize when I come into a multi-team Python shop.
Key facts, with sources
- Python 3.9 reached end of life on October 9, 2025, and Python 3.10 loses security support in October 2026, so enterprises on those versions no longer receive (or will soon stop receiving) security patches. (endoflife.date)
- Roughly 8 to 10 percent of active Python developers were still running the by-then unsupported Python 3.9 in production as of late September 2025. (Medium)
- The Python Package Index surpassed 690,000 hosted projects during 2025, giving enterprise teams an enormous but security-vetting-intensive dependency ecosystem. (PyPI Blog)
- CPython's free-threaded build, which disables the global interpreter lock so threads can run in parallel across CPU cores, is officially supported and no longer considered experimental as of Python 3.14. (Python official documentation)
- Benchmark testing of Python 3.13's free-threaded mode showed multi-threaded tasks executing nearly twice as fast as under the GIL-enabled build, at the cost of some single-threaded overhead. (CodSpeed)
Lockfiles are the baseline, not the goal
Every deployable needs a lockfile that pins the full transitive tree with hashes, committed to the repo, and an install command that fails on any deviation — uv sync --frozen or equivalent. This is what makes builds reproducible: the artifact you tested is built from exactly the packages you reviewed, and yesterday's image can be rebuilt next year for an incident investigation.
At scale, the lockfile earns a second job: it is your dependency inventory. When a vulnerability drops, grepping lockfiles across repos answers "where are we exposed" in minutes. Teams that install from loose requirements files with >= constraints cannot answer that question at all, and typically discover their exposure from the incident rather than the advisory.
Standardize on one tool across the organization
The specific tool matters less than there being exactly one. When five teams use five combinations of pip, pip-tools, poetry, and conda, every piece of shared tooling — CI templates, Dockerfiles, security scanning, onboarding docs — forks five ways. I standardize on uv in current engagements: it is fast enough that CI dependency installation stops being a line item, it is pyproject-native, it manages Python versions themselves, and it covers the lockfile, virtualenv, and tool-running workflows that previously took three tools.
The migration is usually mechanical — pyproject.toml already exists or converts easily — and the payoff is that one reusable CI workflow and one Dockerfile pattern serve the whole organization. Exceptions need a written reason, or the standard erodes one special case at a time.
A constraint policy people can follow
Version constraints cause endless bikeshedding without a written policy. Mine is short. Applications: declare direct dependencies with lower bounds and rely on the lockfile for exactness — exact pins in pyproject.toml are redundant with the lock and make every upgrade a two-file edit. Internal libraries: declare the widest range they genuinely support, because a library that pins exact versions holds every consuming application hostage. Upper bounds: only with a documented reason, such as a known breaking major; speculative caps rot into resolution conflicts.
The distinction between apps and libraries is the part teams most often get wrong, and it is the difference between one team upgrading smoothly and six teams deadlocked on a shared package.
[project]
name = "billing-service"
requires-python = ">=3.11"
dependencies = [
"fastapi>=0.115",
"sqlalchemy>=2.0",
"pydantic>=2.7",
# Upper bound only with a documented reason:
"legacy-sdk>=1.4,<2", # 2.x removed the sync client we use
]
[dependency-groups]
dev = [
"pytest>=8",
"mypy>=1.10",
"ruff>=0.6",
]Internal packages and an internal index
Once several services share code — auth clients, logging setup, canonical models — copy-paste stops scaling and a shared internal library needs a real home. That means an internal package index (or a private index feature of your artifact platform), versioned releases with changelogs, and the same CI rigor as any service. Installing shared code via git URLs works briefly and then breaks caching, resolution, and auditability.
The index also becomes your supply-chain control point: configure it as a pull-through mirror of the public index and you gain availability insulation, an audit log of what enters the organization, and a place to enforce blocklists when a package is compromised. In regulated environments this usually stops being optional at the first serious audit.
Upgrades as a weekly routine, not an annual event
Dependency debt compounds: each skipped month makes the eventual upgrade larger, riskier, and easier to defer again, until a critical CVE forces a giant, untested jump. The escape is cadence. Automated tooling opens grouped upgrade PRs weekly; CI — tests, types, audit — is the reviewer for patch and minor bumps, and merging them should be near-mechanical. Major versions get a human: read the changelog, check the migration notes, upgrade deliberately.
Security advisories run on a separate, faster clock. pip-audit in CI fails builds on known CVEs, and a critical advisory in a widely used package triggers same-week patching across affected repos — which is only feasible because the weekly routine kept every repo close to current. Boring, frequent upgrades are the whole strategy.
When to hire senior help
Senior expertise is most valuable for enterprise Python during interpreter and framework upgrade projects, dependency and supply-chain hardening, and introducing typing to a large untyped codebase, all of which are high-blast-radius changes that reward prior experience. If your core system runs on an end-of-life Python version, treat the migration as a project needing experienced ownership rather than background maintenance. If your stack includes React Native + Python + AI, a senior engineer who owns the full product beats coordinating multiple juniors.
Bottom line
Dhairya Senjaliya ships Python — Enterprise Python Applications projects worldwide — book a scoping call to discuss your specific situation.
Common pitfalls to avoid
- ✕Pinning production to an end-of-life interpreter like Python 3.9 to avoid dependency work, forfeiting security patches and the significant free performance gains of newer releases
- ✕Growing a large codebase without type hints or mypy enforcement, making refactors so risky that feature velocity collapses after a few years
- ✕Installing dependencies without lock files or hash pinning, leaving builds irreproducible and exposed to typosquatted or compromised PyPI packages
- ✕Scaling CPU-bound workloads by adding threads under the GIL, then blaming Python when throughput stays flat instead of using multiprocessing, native extensions, or the free-threaded build
Frequently asked questions
Should I use uv or Poetry in 2026?
For new projects and organization-wide standardization I default to uv: dependency resolution and installation are dramatically faster, it manages Python versions as well as packages, and it works from standard pyproject.toml metadata. Poetry remains a solid, mature choice, and a working Poetry setup is not urgent to migrate. What matters at scale is picking exactly one tool, encoding it in shared CI templates, and eliminating per-team drift.
Should Python applications pin exact dependency versions?
Pin exactness in the lockfile, not in pyproject.toml. The lockfile pins the entire transitive tree with hashes, which is what gives you reproducible builds. Direct dependencies in pyproject.toml should carry lower bounds and only well-justified upper bounds — duplicating exact pins there adds friction to every upgrade without adding safety. Internal libraries are the opposite case: they should declare wide ranges so consuming applications can resolve freely.
How often should teams update Python dependencies?
Weekly, via automated grouped PRs that CI validates — patch and minor bumps should merge near-mechanically, while major versions get a human review of changelogs and migration notes. Security advisories move faster: a critical CVE in a production dependency warrants a same-week patch, which is only realistic if routine upgrades have kept you close to current. Annual big-bang upgrades are the most expensive possible schedule.
Does Python actually scale for enterprise workloads?
Yes, with the right architecture: horizontal scaling, async I/O, and pushing hot loops into native extensions handle most workloads, and the officially supported free-threaded build now removes the GIL for parallel CPU work. The practical scaling limits are architectural, not language-level, for the vast majority of enterprise systems.
How big a risk is Python's open-source supply chain?
PyPI hosts over 690,000 projects, and malicious or typosquatted packages appear regularly, so unpinned installs are a genuine exposure. Standard mitigations are lock files with hashes, dependency scanning in CI, and internal package mirrors, which reduce the risk to a manageable level.
What does staying on an old Python version really cost us?
After end of life, such as Python 3.9 in October 2025, you receive no security patches, and third-party libraries progressively drop support, making the eventual forced upgrade larger and riskier. Newer interpreters also ship substantial performance improvements, so delaying upgrades pays a compounding tax in both risk and compute cost.
Bottom line: Dhairya Senjaliya ships Python — Enterprise Python Applications projects worldwide. Book a scoping call at https://dhairyasenjaliya.com/#book-call.