Two sets of removals sit between 3.9 and 3.13
Python 3.12 removed distutils, imp, asyncore, asynchat and smtpd, removed old unittest aliases such as assertEquals, and stopped installing setuptools in new virtual environments, so a project that needs setuptools or pkg_resources at runtime has to declare it. The release notes give replacements: importlib.util.find_spec in place of imp.find_module, asyncio in place of asyncore and asynchat, and an asyncio-based package for smtpd. Python 3.13 then removed the 19 modules of PEP 594, including cgi, cgitb, crypt, imghdr, mailcap, nntplib, pipes, sndhdr, telnetlib and uu, along with 2to3 and lib2to3.
- cgi and cgitb: the notes suggest urllib.parse for query strings and the email package or a library for form data.
- pipes: use subprocess, with shlex.quote in place of pipes.quote.
- telnetlib, crypt and others: the notes list third-party or hashlib-based alternatives.
Find removed modules without waiting for a crash
An import of a removed module fails when the importing file is loaded, so a test run that never loads that file passes. Search your own code for the module names above, search the installed dependencies that you import for the same names, and then import every module of the application once on the new interpreter in a copy. This is read-only and needs no secrets. Record each hit with its replacement and a test that exercises it.
- Include scripts, management commands and migrations, which tests often skip.
- A dependency that imports a removed module needs a newer release, not a patch in your code.
If the matrix is wider than the box, scroll horizontally to read every column. Keyboard: focus the matrix and use Left/Right.
grep -rnE "^(import|from) (distutils|imp|asyncore|asynchat|smtpd|cgi|cgitb|crypt|imghdr|pipes|telnetlib)\b" --include=*.py .Why a compiled package may not install
Packaging metadata describes a wheel with a Python tag, an ABI tag and a platform tag, and an installer will use only a wheel whose tags match its environment. A compiled wheel built for one CPython version is not interchangeable with another unless it is tagged for the stable ABI; pure-Python wheels work across versions. When no matching wheel exists, the installer builds from source, which needs a compiler and system libraries that a minimal image may lack. The fix is a newer release of the package, a different package or a change to the image, not a longer timeout.
- Look in the install log for compiler output: it signals a source build.
- Ask each compiled package's maintainers which Python versions they publish wheels for.
Pins, hashes and the consistency check
Pinned requirements make a clean install repeatable. pip's hash-checking mode is all-or-nothing: it requires every requirement to be pinned and every dependency to carry a hash, so changing any version means regenerating the hashes. After installing on the new interpreter, run the consistency check, which reports packages whose requirements are not satisfied and exits non-zero if any are not.
- Create a clean environment for each attempt rather than reusing an old one.
- Keep the old lockfile until the new install and tests pass.
What does not fit, and how the fixed job is accepted
This guide does not cover Python 2 code, a Django or Flask major upgrade, or rewriting for performance; those are separate pieces of work. The fixed Python 3.13 job is accepted when a clean install on 3.13 finishes and the consistency check passes, the tests match the baseline, no removed module is imported by your own code and every module imports, and the version is the same in each place it is set. Send the interpreter version and the names of compiled packages, not environment files. Prices are untested proposals; payment follows the agreed checks.
Sources and limits
- What's new in Python 3.12 Checked 2026-10-11.
- Python 3.12 removed distutils, imp, asyncore, asynchat and smtpd, and removed the unittest aliases such as assertEquals and assertRegexpMatches.
- venv no longer installs setuptools by default, so projects that need setuptools or pkg_resources at runtime should declare it as a dependency.
- What's new in Python 3.13 Checked 2026-10-11.
- Python 3.13 removed the 19 PEP 594 modules, including cgi, cgitb, crypt, imghdr, mailcap, nntplib, pipes, sndhdr, telnetlib and uu, and removed 2to3 and lib2to3.
- Python Packaging: platform compatibility tags Checked 2026-10-11.
- A wheel's tags are the Python tag, ABI tag and platform tag, and an installer installs only a wheel whose tags its environment supports; abi3 and pure-Python wheels are tagged to work across versions.
- pip: secure installs Checked 2026-10-11.
- Hash-checking mode is all-or-nothing, requires every requirement to be pinned and every dependency to have a hash.
- pip check Checked 2026-10-11.
- pip check confirms that installed packages' dependency requirements are satisfied, exiting with status 1 if not.