Python 3.12 End of Life: Dates and How to Upgrade

Python 3.12 reaches end of life on 31 October 2028 and gets security fixes only now. What that means, when to move, and what breaks going to 3.13 or 3.14.

Python 3.12 reaches end of life on 31 October 2028. Until then it receives security fixes only. Its bugfix support ended on 2 April 2025 (the last regular bugfix release, 3.12.10, followed on 8 April), and when the date passes the core team stops releasing fixes of any kind.

The full table for every version is on the Python end-of-life dates page; if you are still on an older version, see Python 3.10 end of life. This guide covers what the 3.12 date means in practice and how to move off it.

Python 3.12 dates

MilestoneDate
Released2 October 2023
Bugfix support ended2 April 2025
End of life31 October 2028
Latest release (3.12.16)25 October 2026

These dates come from endoflife.date, which tracks the Python Developer’s Guide. Every feature release gets about five years of support: roughly two of bugfix releases, then security fixes only. There is no separate long-term-support version.

What “security fixes only” means

For Python itself, very little changes day to day. New 3.12 releases still arrive, but they contain security patches rather than bug fixes. A bug that isn’t a vulnerability will be fixed in newer versions and not in 3.12.

The practical pressure comes from outside: libraries drop old Python versions on their own schedule. Pillow 13, for example, dropped Python 3.10 (see the Pillow security guide). Expect the same to happen to 3.12 well before 2028.

Which version to move to

TargetSupported untilNotes
3.13October 2029Smallest jump from 3.12
3.14October 2030A good default target: a year old, with a long support window
3.15October 2031Released 9 October 2026; start on 3.15.2 or later, see below

For most projects, 3.14 is the sensible target. Python 3.15 is worth considering for new work, but libraries often need weeks or months to publish wheels for a new release. See what’s new in Python 3.15 and porting to Python 3.15.

What breaks going from 3.12 to 3.13

The Python 3.13 release notes list the changes. The ones most likely to break an upgrade:

  • Removed standard library modules (PEP 594). Nineteen “dead batteries” are gone, including cgi, cgitb, crypt, imghdr, pipes, telnetlib, uu and xdrlib. An import of any of them raises ImportError. The release notes name replacements: shlex.quote() for pipes.quote, email.message.EmailMessage for cgi.parse_header(), and base64 for uu.
  • Other removals: lib2to3 and 2to3, tkinter.tix, locale.resetlocale(), and the typing.io and typing.re namespaces.
  • locals() semantics (PEP 667). In a function, each call to locals() now returns an independent snapshot, so code that expected earlier results to update will break.
  • pathlib.Path.glob(). A pattern ending in ** now returns files as well as directories. Add a trailing slash to keep the old behaviour.
  • functools.partial as a method now emits a FutureWarning; wrap it in staticmethod() to keep the old behaviour.

What breaks going from 3.13 to 3.14

  • Removed APIs: the asyncio child watcher classes, ast.Num, ast.Str and related aliases, pkgutil.get_loader(), urllib.request.URLopener, and the importlib.abc resource classes (use importlib.resources.abc).
  • asyncio.get_event_loop() raises RuntimeError when there is no current loop instead of creating one. Use asyncio.run().
  • Deferred annotations (PEP 649). Annotations are no longer evaluated eagerly. Code that reads __annotations__ directly may behave differently; the new annotationlib module lets you choose a format.
  • Multiprocessing default. On Unix other than macOS, the default start method is now forkserver rather than fork. Code that relies on forked children inheriting globals will break.

A safe way to upgrade

  1. Run your tests on the new version first, with warnings turned on: python -W error -m pytest. Deprecation warnings on 3.12 are removals on 3.13 and 3.14.
  2. Search for removed imports before you start. A quick grep for import cgi, telnetlib and pipes finds most of the standard library fallout.
  3. Check your dependencies publish wheels for the target version. Packages with compiled extensions are the usual blockers.
  4. Move one minor version at a time if the codebase is large, so a failure points at one set of changes.
  5. Rebuild anything pinned to 3.12: CI images, containers and lockfiles. A tool like uv can install the new interpreter and create the environment in one step.

Frequently Asked Questions

When does Python 3.12 reach end of life?

31 October 2028. Python 3.12 was released on 2 October 2023, had bugfix support until 2 April 2025, and now receives security fixes only until that date.

Is Python 3.12 still supported?

Yes, but only for security fixes. The last regular bugfix release, 3.12.10, came out on 8 April 2025. The latest 3.12 release at the time of writing is 3.12.16, from 25 October 2026.

Do I have to upgrade from Python 3.12 now?

No. You have until October 2028 before 3.12 stops receiving security fixes. Plan the move within that window, and sooner if a dependency drops 3.12 or you want a newer feature.

Which Python version should I move to from 3.12?

Python 3.14 is the safer target for most projects: it is a full release behind the newest version and supported until October 2030. Python 3.13 is supported until October 2029. Python 3.15, released in October 2026, is supported until October 2031, but give your dependencies a few months to catch up.

What breaks when upgrading from Python 3.12 to 3.13?

Mostly removed standard library modules, such as cgi, telnetlib, imghdr and pipes (PEP 594), plus lib2to3, tkinter.tix and a few typing and locale helpers. Python 3.13 also changed locals() so that each call in a function returns an independent snapshot.

What breaks when upgrading from Python 3.13 to 3.14?

Removed APIs including the old asyncio child watchers, ast.Num and ast.Str, pkgutil.get_loader and urllib.request.URLopener. Annotations are also evaluated lazily, and on Linux the default multiprocessing start method changed from fork to forkserver.