# 68. Limited API

# 68. Limited API

The limited API is a restricted source-level subset of the CPython C API. It is selected at compile time and designed to let extension modules target the stable ABI.

The stable ABI is the binary promise. The limited API is the programming surface used to reach that promise.

```text
extension source code
    ↓
#define Py_LIMITED_API
    ↓
limited API declarations
    ↓
stable ABI compatible binary
    ↓
abi3 wheel
```

The limited API makes many CPython implementation details opaque. Extension code uses documented functions and supported type creation mechanisms instead of direct struct access.

## 68.1 Limited API vs Stable ABI

The two terms are related but distinct.

| Concept | Meaning |
|---|---|
| Limited API | Header-level source subset exposed during compilation |
| Stable ABI | Binary interface that remains compatible across Python versions |
| `Py_LIMITED_API` | Macro that enables the limited API |
| `abi3` | Wheel tag for stable ABI binaries |

The limited API controls what your C compiler sees.

The stable ABI controls what your compiled extension can link against and load with.

## 68.2 Enabling the Limited API

A source file opts in before including `Python.h`:

```c
#define Py_LIMITED_API 0x03080000
#define PY_SSIZE_T_CLEAN
#include <Python.h>
```

The version value means:

```text
use the limited API available from Python 3.8 onward
```

Common values:

| Value | Minimum CPython |
|---|---|
| `0x03080000` | 3.8 |
| `0x03090000` | 3.9 |
| `0x030A0000` | 3.10 |
| `0x030B0000` | 3.11 |
| `0x030C0000` | 3.12 |
| `0x030D0000` | 3.13 |

Choose the oldest version you want to support. A lower value gives wider runtime compatibility but fewer available APIs.

## 68.3 What the Macro Changes

Without `Py_LIMITED_API`, `Python.h` exposes the normal CPython API, including many CPython-specific details.

With `Py_LIMITED_API`, headers hide or restrict implementation details.

Code like this becomes less acceptable:

```c
Py_TYPE(obj)->tp_name
```

Limited API code should use supported functions:

```c
PyObject *type = PyObject_Type(obj);
```

The goal is to prevent extension code from depending on layouts that CPython may change.

## 68.4 Opaque Structures

The limited API treats many structures as opaque.

Opaque means:

```text
you may hold a pointer
you may pass it to API functions
you may not inspect its fields directly
```

Examples:

| Structure | Limited API style |
|---|---|
| `PyObject` | Use object APIs |
| `PyTypeObject` | Use type APIs and heap type specs |
| `PyFrameObject` | Avoid direct frame field access |
| `PyThreadState` | Use documented thread APIs |
| `PyInterpreterState` | Avoid internals |

This changes extension design. Code becomes less like “read this struct field” and more like “ask the runtime through a function.”

## 68.5 Object Access Patterns

Full API code may use direct or macro-based access:

```c
Py_ssize_t n = PyList_GET_SIZE(list);
PyObject *item = PyList_GET_ITEM(list, i);
```

Limited API code should use checked API functions:

```c
Py_ssize_t n = PyList_Size(list);
PyObject *item = PyList_GetItem(list, i);
```

The difference matters.

| API style | Behavior |
|---|---|
| Uppercase macro | Often unchecked, may rely on internals |
| Function API | Safer, stable ABI friendly |

The limited API prefers function calls that preserve ABI boundaries.

## 68.6 Reference Ownership Still Applies

The limited API does not change ownership rules.

Example:

```c
PyObject *s = PyUnicode_FromString("hello");
if (s == NULL) {
    return NULL;
}

/* use s */

Py_DECREF(s);
```

`PyUnicode_FromString` returns a new reference.

Borrowed references remain borrowed:

```c
PyObject *item = PyList_GetItem(list, 0);
```

Do not decref `item` unless you first incref it.

The limited API hides layout details. It does not make C memory management automatic.

## 68.7 Type Creation with `PyType_Spec`

Limited API code should define heap types using `PyType_Spec`.

Instead of:

```c
static PyTypeObject PointType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "geometry.Point",
    .tp_basicsize = sizeof(PointObject),
    .tp_new = Point_new,
};
```

use:

```c
static PyType_Slot Point_slots[] = {
    {Py_tp_new, Point_new},
    {Py_tp_init, Point_init},
    {Py_tp_dealloc, Point_dealloc},
    {Py_tp_methods, Point_methods},
    {0, NULL}
};

static PyType_Spec Point_spec = {
    .name = "geometry.Point",
    .basicsize = sizeof(PointObject),
    .itemsize = 0,
    .flags = Py_TPFLAGS_DEFAULT,
    .slots = Point_slots,
};
```

Create the type at module initialization:

```c
PyObject *PointType = PyType_FromSpec(&Point_spec);
```

This avoids direct dependence on `PyTypeObject` layout.

## 68.8 Heap Types

Heap types are Python type objects allocated at runtime.

They work better with:

```text
limited API
stable ABI
multi-phase initialization
subinterpreters
per-module state
cleaner finalization
```

A heap type is still a normal Python type:

```python
p = geometry.Point(1, 2)
print(type(p))
```

But its C definition is slot-based rather than struct-literal-based.

## 68.9 Module Creation

Simple module creation remains familiar.

```c
static PyMethodDef methods[] = {
    {"add", add, METH_VARARGS, "Add two integers"},
    {NULL, NULL, 0, NULL}
};

static struct PyModuleDef module = {
    PyModuleDef_HEAD_INIT,
    "demo",
    "Limited API demo",
    -1,
    methods
};

PyMODINIT_FUNC
PyInit_demo(void)
{
    return PyModule_Create(&module);
}
```

This style is compatible with limited API usage when the functions used are in the supported subset.

## 68.10 Multi-Phase Initialization

For more robust extension modules, limited API design pairs well with multi-phase initialization.

Conceptual structure:

```text
module definition
    ↓
module creation slot
    ↓
module execution slot
    ↓
create heap types
    ↓
initialize per-module state
```

This avoids many process-global assumptions.

Benefits:

| Feature | Benefit |
|---|---|
| Per-module state | Better interpreter isolation |
| Heap types | Less static runtime state |
| Execution slot | Cleaner initialization |
| Reload handling | More predictable lifecycle |

## 68.11 Per-Module State

Limited API extensions should prefer module state over global variables.

Avoid:

```c
static PyObject *DemoError;
```

Prefer a module state structure:

```c
typedef struct {
    PyObject *DemoError;
    PyObject *PointType;
} demo_state;
```

The module definition can reserve state:

```c
static struct PyModuleDef module = {
    PyModuleDef_HEAD_INIT,
    "demo",
    "Demo module",
    sizeof(demo_state),
    methods
};
```

Then retrieve it:

```c
demo_state *st = PyModule_GetState(module);
```

This is better for subinterpreters because each module instance can have its own state.

## 68.12 Function Calls

Limited API code can still call Python objects.

```c
PyObject *result = PyObject_CallObject(func, args);
```

It can also call methods:

```c
PyObject *result =
    PyObject_CallMethod(obj, "close", NULL);
```

The usual error rule applies:

```text
non-NULL result = success
NULL result = exception set
```

Always check return values before continuing.

## 68.13 Attribute Access

Use generic attribute APIs:

```c
PyObject *value =
    PyObject_GetAttrString(obj, "name");
```

Set:

```c
if (PyObject_SetAttrString(obj, "name", value) < 0) {
    return NULL;
}
```

These APIs preserve Python semantics:

```text
descriptors
properties
custom __getattribute__
class attributes
instance dictionaries
```

Limited API code should avoid bypassing these mechanisms through direct layout assumptions.

## 68.14 Lists, Tuples, and Dicts

You can still use core containers.

List example:

```c
PyObject *list = PyList_New(0);
if (list == NULL) {
    return NULL;
}

PyObject *value = PyLong_FromLong(42);
if (value == NULL) {
    Py_DECREF(list);
    return NULL;
}

if (PyList_Append(list, value) < 0) {
    Py_DECREF(value);
    Py_DECREF(list);
    return NULL;
}

Py_DECREF(value);
return list;
```

Dict example:

```c
PyObject *dict = PyDict_New();
if (dict == NULL) {
    return NULL;
}

if (PyDict_SetItemString(dict, "answer", PyLong_FromLong(42)) < 0) {
    Py_DECREF(dict);
    return NULL;
}
```

The second example leaks the temporary integer because it does not store and decref it. Correct version:

```c
PyObject *dict = PyDict_New();
if (dict == NULL) {
    return NULL;
}

PyObject *answer = PyLong_FromLong(42);
if (answer == NULL) {
    Py_DECREF(dict);
    return NULL;
}

if (PyDict_SetItemString(dict, "answer", answer) < 0) {
    Py_DECREF(answer);
    Py_DECREF(dict);
    return NULL;
}

Py_DECREF(answer);
return dict;
```

Limited API does not protect against ordinary reference bugs.

## 68.15 Unicode and Bytes

Unicode and bytes APIs are available through stable functions.

Create Unicode:

```c
PyObject *s = PyUnicode_FromString("hello");
```

Convert to UTF-8 bytes:

```c
PyObject *b = PyUnicode_AsUTF8String(s);
```

Create bytes:

```c
PyObject *b = PyBytes_FromStringAndSize(buf, len);
```

Accessing raw internal Unicode layout is not appropriate for limited API code. Use conversion and accessor functions instead.

## 68.16 Buffers

The buffer protocol can be used through supported APIs.

Consumer pattern:

```c
Py_buffer view;

if (PyObject_GetBuffer(obj, &view, PyBUF_SIMPLE) < 0) {
    return NULL;
}

/* use view.buf and view.len */

PyBuffer_Release(&view);
```

This remains useful for stable ABI extensions that wrap native libraries expecting memory ranges.

## 68.17 Capsules

Capsules fit well with limited API design.

They allow one extension to export a stable C function table:

```c
PyObject *capsule =
    PyCapsule_New(&Demo_API, "demo._C_API", NULL);
```

A consumer imports and validates:

```c
DemoAPI *api =
    PyCapsule_GetPointer(capsule, "demo._C_API");
```

For stable extension ecosystems, include version and size fields in the exported table.

```c
typedef struct {
    int version;
    size_t size;
    PyObject *(*make_value)(long);
} DemoAPI;
```

## 68.18 Error Handling

Limited API error handling follows the normal C API convention.

For functions returning `PyObject *`:

```text
success = new reference
failure = NULL with exception set
```

For integer status functions:

```text
success = 0
failure = -1 with exception set
```

Example:

```c
PyObject *obj = PyObject_CallObject(func, args);
if (obj == NULL) {
    return NULL;
}
```

Never continue after a failed API call unless you intentionally handle or clear the exception.

## 68.19 What to Avoid

Limited API code should avoid:

```text
Include/cpython/
Include/internal/
private _Py* functions
direct PyTypeObject field access
direct PyFrameObject field access
unchecked macros that depend on layout
global borrowed references
static mutable Python object state
version-specific bytecode assumptions
```

If your code requires these, it probably needs the full CPython API.

## 68.20 Common Porting Changes

When converting full API code to limited API code, changes often look like this:

| Full API pattern | Limited API replacement |
|---|---|
| Static `PyTypeObject` | `PyType_Spec` heap type |
| `PyList_GET_ITEM` | `PyList_GetItem` |
| `PyList_GET_SIZE` | `PyList_Size` |
| Direct `tp_*` access | Type APIs or slots |
| Direct frame fields | Public frame APIs where available |
| Global module state | Per-module state |
| Private `_Py*` helper | Public API equivalent or local code |

Some code ports cleanly. Some code needs architectural change.

## 68.21 Build Configuration

A build must define the macro and request limited API output.

Setuptools example:

```python
from setuptools import Extension, setup

setup(
    name="demo",
    ext_modules=[
        Extension(
            "demo",
            ["demo.c"],
            py_limited_api=True,
            define_macros=[
                ("Py_LIMITED_API", "0x03080000"),
            ],
        )
    ],
)
```

The wheel must also use an `abi3` tag. Build tooling needs to be configured correctly, not only the C source.

## 68.22 Practical Decision Rule

Use the limited API when your extension:

```text
wraps a C library
does coarse-grained native work
does not inspect interpreter internals
does not rely on private APIs
can define heap types
benefits from one wheel across Python versions
```

Avoid it when your extension:

```text
needs frame internals
modifies type internals directly
uses private CPython functions
depends on exact object layout
implements a low-level profiler or debugger
needs maximum speed for tiny object operations
```

## 68.23 Common Mistakes

| Mistake | Result |
|---|---|
| Defining `Py_LIMITED_API` too late | Headers already exposed full API |
| Mixing internal headers | Breaks limited API discipline |
| Publishing non-abi3 wheel | Loses binary portability benefit |
| Using static `PyTypeObject` | Couples to type layout |
| Assuming limited API removes refcounting | Still leaks or crashes |
| Caching interpreter-specific globals | Subinterpreter problems |
| Relying on unchecked macros | ABI fragility |

## 68.24 Chapter Summary

The limited API is the source-level subset of CPython’s C API used to build stable ABI extensions. It is enabled with `Py_LIMITED_API` before including `Python.h`.

It hides many implementation details and encourages extension code to use function APIs, heap types, module state, capsules, buffer interfaces, and documented runtime operations.

The limited API trades some power and sometimes some speed for binary portability. It is a good fit for native library wrappers, binary parsers, codecs, database adapters, and extensions that do most of their work outside Python object internals.
