Python 3.15.0rc2 was published on September 1, 2026 as the last planned release candidate, and Python 3.15.0 final is scheduled for October 1. From the release-candidate phase onward, the project says there will be no further ABI changes in the 3.15 series and code changes should be limited to clear bug fixes. For maintainers of libraries or native extensions, that makes RC2 the best window to find incompatibilities before the final release.
The risk is not limited to whether code compiles. There are removed C API symbols, Python APIs that disappeared or became stricter, and behavior changes that can break tests without causing an import error. The guide below separates those three groups and, for each portability-impacting item, gives the recommended migration and a minimum test.
RC2 is the testing window that matters
The September 1 RC2 is the last planned release candidate. The official schedule still targets Python 3.15.0 final for October 1, 2026. The RC2 page also states that there will be no ABI changes from this point through the 3.15 series, with the broader goal of keeping code changes to a minimum. That does not make RC2 a production-recommended release, but it makes its compatibility results especially useful.
The focus here is code and extension portability, not wheel packaging. The essential test is to run the suite on 3.15 RC2 and, where C or C++ is involved, rebuild the extension from source against the 3.15 headers.
Removed or tightened Python APIs
| Area | What changes in 3.15 | Replacement or adjustment | Minimum test |
|---|---|---|---|
| ast | AST node constructors now raise TypeError when required fields are missing or names that do not map to valid fields are passed. | Instantiate nodes with all required fields and only documented arguments. | Run tests that create or transform ASTs and force execution of code-generation paths. |
| collections.abc.ByteString | ByteString was removed from collections.abc.__all__; the symbol remains deprecated and is scheduled for removal in 3.17. | For the buffer protocol at runtime, use collections.abc.Buffer; for typing, use Buffer or an explicit union such as bytes | bytearray | memoryview. | Test explicit imports and any star import that expected ByteString to be present. |
| ctypes.SetPointerType() | The undocumented function was removed. | There is no documented 1:1 replacement; prefer public pointer APIs such as ctypes.POINTER(T) and remove reliance on internals. | Import the module that constructs ctypes types and exercise creation, reading, and writing of the pointers the library uses. |
| datetime.strptime() | A format containing %d without a year directive can now raise ValueError. | Include the year in both the input and format string when the day of month is parsed. | Add a case using the old format and confirm that real input now includes a year. |
| glob.glob0()/glob.glob1() | The undocumented internal functions were removed. | Use glob.glob() and pass root_dir when a search must be scoped to a directory. | Compare the returned path set before and after migration in a temporary directory tree. |
| http.server CGI | CGIHTTPRequestHandler and the --cgi option of python -m http.server were removed. | There is no 1:1 CGI replacement in the standard library; remove the dependency and run the application with an appropriate server solution. | Smoke-test the development or integration command that previously started CGI mode. |
| importlib loaders | Loader.load_module() and corresponding implementations were removed from the protocol used by the import system. | Implement exec_module(); when needed, also implement create_module() according to the modern protocol. | Install the custom finder/loader in sys.meta_path or sys.path_hooks and import/reload a real module. |
| importlib.resources.files() | The package keyword parameter was removed. | Pass anchor, preferably positionally. For an interface compatible with older Pythons, the documentation suggests importlib_resources >= 5.10. | Read a package resource using the exact call used in production. |
| pathlib.PurePath.is_reserved() | The method was removed. | Use os.path.isreserved(). | Test reserved and ordinary names, especially on Windows. |
| platform.java_ver() | The function was removed; it targeted Jython support and had an API considered confusing and largely untested. | There is no direct CPython replacement; remove the dependency or obtain Java runtime data from the platform that actually provides it. | Search for calls and execute initialization in any Java/Jython environment the project still supports. |
| sre_compile, sre_constants, sre_parse | The internal sre_* modules were removed. | Migrate to public re APIs such as re.compile(), Pattern, and documented functions. Do not treat parser internals as a stable contract. | Run regex compilation and introspection tests that previously imported sre_* directly. |
| sysconfig.is_python_build(check_home=...) | The check_home parameter was removed. | Call sysconfig.is_python_build() without that argument and adjust caller logic if it relied on the old behavior. | Test detection in a build tree and in a normal installation. |
| threading.RLock() | The C implementation no longer accepts arbitrary positional or keyword arguments that used to be ignored. | Instantiate RLock() without arguments. | Search for RLock( in factories and wrappers and execute the path that creates each lock. |
| types.CodeType.co_lnotab | The co_lnotab attribute was removed. | Use code.co_lines() to map bytecode ranges to lines; use documented position APIs when richer location data is needed. | Run profilers, tracers, coverage, or inspection tools that read code-object metadata. |
| typing.ByteString | ByteString was removed from typing.__all__; it remains deprecated and is scheduled for removal in 3.17. | Use collections.abc.Buffer or explicit types that match the function’s contract. | Test star imports and run the type checker used by the project. |
| typing.NamedTuple | The undocumented NamedTuple("Point", x=int, y=int) keyword syntax is no longer accepted. | Use the class-based syntax or the documented functional syntax. | Import every module that defines NamedTuple types at runtime and instantiate each type. |
| typing.TypedDict | TypedDict("TD") and TypedDict("TD", None) for zero fields are no longer accepted. | Use class TD(TypedDict): pass or TypedDict("TD", {}). | Import typing modules at runtime and run the type checker to confirm equivalent behavior. |
| typing.no_type_check_decorator | The decorator was removed. | When genuinely needed, apply typing.no_type_check directly to the function or class whose annotations should be ignored. | Search for the removed name and run both import and static checking of the migrated module. |
| wave getmark()/setmark()/getmarkers() | The compatibility methods for the former aifc module were removed. | There is no equivalent replacement; eliminate calls that depended on these markers. | Read and write a representative WAV and confirm no path calls the removed methods. |
| zipimport.zipimporter.load_module() | The method was removed. | Use exec_module() in the modern import flow. | Create a test ZIP containing a module and validate import and reload through the loader used by the project. |
C API: symbols that will stop compiling
In C/C++ extensions, the first signal is usually a compiler or linker error. The removals below are documented in the 3.15 “What’s New”. For replacements that return objects, review ownership rules as well: for example, PyWeakref_GetRef() returns a new strong reference when the target is alive.
| Removed API | Official replacement | Minimum test |
|---|---|---|
| PyUnicode_AsDecodedObject() / PyUnicode_AsDecodedUnicode() | PyCodec_Decode(). | Build from scratch and test at least one text codec plus any codec that may return bytes. |
| PyUnicode_AsEncodedObject() / PyUnicode_AsEncodedUnicode() | PyCodec_Encode(). | Build from scratch and validate the returned type for the codecs used by the extension. |
| PyImport_ImportModuleNoBlock() | PyImport_ImportModule(). | Import the dependent module during normal extension initialization and confirm error propagation. |
| PyWeakref_GetObject() / PyWeakref_GET_OBJECT | PyWeakref_GetRef(). For older Pythons, the documentation points to pythoncapi-compat. | Test a live weakref, a dead weakref, and the error path, checking INCREF/DECREF of the new reference. |
| PySys_ResetWarnOptions() | Clear sys.warnoptions and warnings.filters as directed by the official documentation. | Configure filters, invoke the migrated routine, and confirm final state and emitted warnings. |
| Py_GetExecPrefix() | PyConfig_Get("base_exec_prefix"); use "exec_prefix" when a virtual environment must be reflected. | Compare with sys.base_exec_prefix and sys.exec_prefix in a normal install and a venv. |
| Py_GetPath() | PyConfig_Get("module_search_paths"). | Compare the list with sys.path after interpreter initialization. |
| Py_GetPrefix() | PyConfig_Get("base_prefix"); use "prefix" to reflect a venv. | Compare with sys.base_prefix and sys.prefix inside and outside a venv. |
| Py_GetProgramFullPath() / Py_GetProgramName() | PyConfig_Get("executable"). | Validate the path in an embedding executable and in the standard interpreter. |
| Py_GetPythonHome() | PyConfig_Get("home") or the PYTHONHOME environment variable. | Test with and without PYTHONHOME and confirm the value after initialization. |
PyConfig_Get() is a runtime configuration API: its documentation requires an attached thread state and says it cannot be called before Python initialization or after finalization. If an embedder used the old Py_Get* calls before initializing Python, migration must revisit the configuration phase with the modern initialization APIs rather than simply swapping function names.
Two C API changes that also deserve a static search
- If a type sets Py_TPFLAGS_MANAGED_DICT or Py_TPFLAGS_MANAGED_WEAKREF, it must also set Py_TPFLAGS_HAVE_GC. Add a test that creates, references, collects, and destroys instances of the type.
- PyDateTime_IMPORT is now thread-safe. Code that checked PyDateTimeAPI directly against NULL should call PyDateTime_IMPORT. Exercise import and DateTime C API usage in the extension’s real path.
Behavior changes that can break tests
| Area | Change | How to test |
|---|---|---|
| sqlite3 | In sqlite3.connect(), every parameter except database is now keyword-only; in create_function() and create_aggregate(), the first three are positional-only; set_authorizer(), set_progress_handler(), and set_trace_callback() also tightened the position of their first argument. | Run tests that create connections and register callbacks using the exact public signatures exposed by the project. |
| resource | resource.RLIM_INFINITY is now always positive; negative integers matching its old value are deprecated. | Compare limits read and written on each supported platform and stop storing -1/-3 as portable sentinels. |
| mmap | mmap.mmap.resize() is now absent on platforms without support for the underlying system call instead of existing and raising SystemError. | Use hasattr() on the target platform and test the application fallback when resize is unavailable. |
| ElementTree.iterparse() | An iterator that opened a file and is not closed may now emit ResourceWarning. | Run tests with warnings treated as errors and close the iterator explicitly with close() or contextlib.closing(). |
| argparse | With a short option and a single-hyphen long option, such as -f and -foo, dest is now inferred from -foo. | Assert the final Namespace; if old behavior is required, pass dest explicitly. |
| base64 | urlsafe_b64decode() no longer requires input padding. | Test inputs with and without padding; if the product requires padding, use padded=True or b64decode(..., altchars=b'-_'). |
| unittest | assertWarns() and assertWarnsRegex() no longer discard non-matching warnings, so warnings that used to be hidden may escape the block. | Run the suite with stricter warning filters and handle every expected warning explicitly. |
| Native extensions and frame pointers | CPython is now built with frame pointers enabled by default. Native libraries with custom builds should keep the unwinding chain intact. | In custom builds, use flags equivalent to those documented by CPython and validate backtraces/crash handlers on a 3.15 build. |
Portability checklist before October 1
- Statically search for removed names. Include sre_, load_module, co_lnotab, package= in importlib.resources.files, Py_Get*, PyWeakref_GetObject, and the removed PyUnicode functions.
- Run the suite on Python 3.15 RC2. Also enable DeprecationWarning in CI to catch work that does not break 3.15 yet but already points to changes for 3.16–3.20.
- Rebuild C/C++ extensions from a clean tree. Do not reuse objects compiled with older headers; the goal is to make the compiler reveal removed references.
- Keep an import smoke test. Import all public modules plus optional modules that load native libraries, custom loaders, ctypes, or package resources.
- Test contracts, not only exceptions. Verify codec return types, C reference ownership, argparse.Namespace values, sqlite3 callbacks, and warning behavior.
- Test venv and embedding separately when using PyConfig. base_prefix/prefix and base_exec_prefix/exec_prefix are not equivalent when a virtual environment is active.
- Record the RC2 baseline. Because the ABI should not change between RC2 and 3.15 final, a green suite and clean build now substantially reduce the October 1 upgrade risk, although bug fixes may still land.
Separate current breakage from future warnings
An actual removal must be fixed for a project to work on 3.15. A new deprecation, by contrast, may not break today but should enter the backlog with its projected removal version. Keeping those categories separate prevents two common mistakes: treating every warning as an emergency or, at the other extreme, ignoring warnings until the API disappears.
For libraries with native extensions, keep two independent checks: compilation against 3.15 and functional runtime testing. Compilation proves that the symbols and signatures you use still exist; functional tests reveal reference-ownership, initialization, callback, and standard-library behavior differences.




