Engineering Practitioner Brief / 18 May 2026

Python and Django Legacy Cost

Python applications tend to live a long time. The Python 2 to 3 migration spanned roughly a decade at industry scale (2008 release of Python 3.0, January 2020 end-of-life of Python 2.7, ongoing tail of enterprise migrations in 2026). Django, the dominant Python web framework, follows a predictable LTS ladder that makes upgrade work routine when current and expensive when deferred. This page collects the per-step costs.

Python 2 to Python 3

The defining Python legacy project of the 2010s and now the 2020s. Python 2.7 reached end-of-life on 1 January 2020 but enterprise installations continued running it. Red Hat shipped patched Python 2 packages for RHEL 7 until June 2024. Some scientific computing pipelines still run Python 2 in 2026 because the libraries they depend on were last updated for Python 2 and no maintainer has stepped forward to port them.

The migration cost has three layers. First, syntactic differences (the print statement, the integer division operator, string vs bytes handling). The 2to3tool from the standard library handles most of these mechanically, as do the Bowler and python-modernize tools. Estimated cost for a 50K-line codebase: 80 to 150 engineer-hours.

Second, dependency replacement. Python 2 era libraries that have not been ported (or have been ported incompatibly) need substitution. Common examples include older versions of SimpleITK, some Twisted-based libraries, and the long tail of bespoke internal forks. This layer is where Python 2 to 3 estimates blow up; for a heavily-libraried codebase the dependency audit alone is 80 to 200 hours and the replacement work can be 200 to 600 hours on top.

Third, runtime semantic differences that 2to3 does not catch. Iteration semantics changed (range, map, filter now return iterators), Unicode handling changed, exception syntax changed. These are real bugs waiting in the codebase. Testing has to be more thorough than for a typical version upgrade. Plan an additional 80 to 200 engineer-hours for test backfill and verification.


The Django LTS Ladder

Django releases an LTS every two years (1.11, 2.2, 3.2, 4.2, 5.x as of 2026). Each LTS is supported for three years of extended security updates. The upgrade path from any LTS to the next is documented and typically incremental. Skipping an LTS doubles the work; the deprecation warnings that fire during a single-step upgrade no longer fire when you skip a version.

StepNotable ChangesHours / 50K LOC
1.11 to 2.2Python 3 required, URL routing change (url() to path()), middleware signature60 to 150
2.2 to 3.2Async views support, removed default app config, JSONField changes30 to 80
3.2 to 4.2zoneinfo replaces pytz, USE_DEPRECATED_PYTZ flag, async ORM partial40 to 100
4.2 to 5.xAsync ORM expansion, removed deprecated features30 to 80
1.11 to 5.x (full)Cumulative150 to 400

The 1.11 to 2.2 step is the heaviest because of the URL routing change and the simultaneous Python 2 to 3 requirement. Most teams that have stayed on Django 1.11 are also still on Python 2. The two migrations have to happen together, which puts the realistic cost at the top of the per-step range or beyond.


Flask and FastAPI Legacy

Flask 0.x to Flask 3 spans more than a decade. The breaking changes are scattered across many minor releases but the cumulative migration is typically lighter than the Django ladder because Flask's surface area is smaller. Plan 40 to 120 engineer-hours per 50K-line Flask app for a Flask 0.x to Flask 3 migration, plus Werkzeug compatibility cleanups.

FastAPI is younger and breaking changes are rare. The main legacy concern for FastAPI codebases is Pydantic v1 to v2, which landed in 2023 and broke validation semantics across the ecosystem. Pydantic v2 is a substantial rewrite; the migration for a 50K-line FastAPI app is typically 30 to 80 hours, mostly mechanical thanks to the Pydantic team's migration script.


Type-Hint Backfill

Python type hints (PEP 484) reached general adoption around 2018. Codebases written before then are almost entirely untyped. Adding type hints retroactively catches a class of bugs that runtime testing misses, improves IDE support, and (importantly) enables static analysis tools like mypy and pyright.

The cost profile is gentle: 0.4 to 1.5 engineer-hours per file for a meaningful type-hint pass. Tools like MonkeyType(Instagram's runtime-tracing type stub generator) reduce the cost by suggesting types from observed runtime behaviour. The engineer still has to review and correct the suggestions, but the starting point is far better than blank.

The payback is steady. A 2019 paper from Google on internal Python codebases reported a measurable reduction in bug-related code churn after typing was enabled, attributed to catch-at-compile-time of a class of attribute-access errors. The pattern is similar to the JavaScript-to-TypeScript story (see JavaScript and Node legacy cost): typed code pays back within months of adoption.


asyncio Adoption

Python's asyncio support has matured since the 2014 introduction in Python 3.4. By Python 3.11 asyncio is fast enough for most production workloads, and by Python 3.12 the standard library async APIs cover most common needs (subprocess, file I/O, HTTP via aiohttp or httpx). Adopting asyncio in a synchronous codebase is a substantial refactor because async and sync function calls do not compose cleanly.

The pragmatic pattern is to introduce asyncio at I/O boundaries: a high-throughput HTTP service moves to an async framework (FastAPI, Starlette, or Django's async views), while background workers and CPU-heavy code remain synchronous. The two layers communicate through queues. The split avoids the function-coloring tax (every async function infects its callers) for the bulk of the codebase.

Cost reference: for a 50K-line Django app, adding async-view support to the 20 percent of endpoints that would actually benefit is 80 to 200 engineer-hours. A wholesale migration is rarely justified by the throughput gain alone; the cost-benefit only flips when the existing synchronous deployment is genuinely saturated and scaling vertically has run out of headroom.


The Packaging Layer

Python packaging is itself a legacy story. setup.py to pyproject.toml, the rise of Poetry / PDM / Rye / uv, the various lockfile formats. The Python Packaging Authority's direction is clear (PEP 621 metadata in pyproject.toml, dependency groups, build backends), but the transition has costs.

Related Reading


Frequently Asked Questions

Are people still on Python 2?

Yes. Python 2.7 reached end-of-life in January 2020, but enterprise installations (especially scientific, financial, and government systems) still run it in 2026. Red Hat continued shipping patched Python 2 packages until June 2024 for RHEL 7. The Python 2 to 3 migration is the canonical example of a programming-language EOL that took a decade to resolve at scale.

How much does Python 2 to 3 cost?

For a 50K-line Python 2 codebase, $35K to $100K. The 2to3 tool handles the syntactic part (print statement to print function, integer division semantics, string vs bytes). The harder cost is library replacement: many Python 2 era libraries have no Python 3 equivalent, and some changed maintainers. Plan for a substantial dependency audit before estimating.

What does Django 1.11 to Django 5 take?

Django supports upgrade from the previous LTS, so the path is 1.11 to 2.2 to 3.2 to 4.2 to 5.x. Each LTS-to-LTS step is typically 30 to 100 engineer-hours per 50K-line app, with the biggest costs at 1.11 to 2.2 (URL routing change, Python 2 to 3) and 3.2 to 4.2 (async views, removed deprecations). Total for 1.11 to 5.x is 150 to 400 engineer-hours.

Should I migrate from Django to FastAPI?

Only when the project's needs have shifted to API-first with heavy async usage. Django's batteries-included philosophy (admin, ORM, migrations, auth) is hard to replace incrementally. A piecemeal migration where new endpoints land in FastAPI while existing Django endpoints stay is common; a full rewrite is rarely worth it for working Django applications.

How much does type-hint backfill cost?

Roughly 0.4 to 1.5 engineer-hours per Python file for meaningful type hints, depending on file complexity. Tools like MonkeyType, pytype, and pyright can suggest type stubs from runtime traces, reducing the cost. For a 200-file Django codebase, plan 80 to 300 engineer-hours for a thorough type-hint pass that catches a meaningful percentage of latent bugs.

Is asyncio adoption worth the migration?

For I/O-heavy workloads, yes. For CPU-heavy or low-concurrency workloads, often not. Adding asyncio support to a synchronous Django codebase is structurally a significant refactor because async functions and sync functions do not compose cleanly. Django's own async view support (added 3.1+) is the lowest-cost path for Django apps that genuinely need it.

Updated 2026-04-27