Skip to content

Commit 26cc7e7

Browse files
Fix annotations support on 3.14 (#852)
1 parent bc60e96 commit 26cc7e7

File tree

2 files changed

+69
-6
lines changed

2 files changed

+69
-6
lines changed

msgspec/_core.c

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,7 @@ typedef struct {
452452
#endif
453453
PyObject *astimezone;
454454
PyObject *re_compile;
455+
PyObject *get_annotate_from_class_namespace;
455456
uint8_t gc_cycle;
456457
} MsgspecState;
457458

@@ -5814,12 +5815,45 @@ structmeta_is_classvar(
58145815

58155816
static int
58165817
structmeta_collect_fields(StructMetaInfo *info, MsgspecState *mod, bool kwonly) {
5817-
PyObject *annotations = PyDict_GetItemString(
5818+
PyObject *annotations = PyDict_GetItemString( // borrowed reference
58185819
info->namespace, "__annotations__"
58195820
);
5820-
if (annotations == NULL) return 0;
5821+
if (annotations == NULL) {
5822+
if (mod->get_annotate_from_class_namespace != NULL) {
5823+
PyObject *annotate = PyObject_CallOneArg(
5824+
mod->get_annotate_from_class_namespace, info->namespace
5825+
);
5826+
if (annotate == NULL) {
5827+
return -1;
5828+
}
5829+
if (annotate == Py_None) {
5830+
Py_DECREF(annotate);
5831+
return 0;
5832+
}
5833+
PyObject *format = PyLong_FromLong(1); /* annotationlib.Format.VALUE */
5834+
if (format == NULL) {
5835+
Py_DECREF(annotate);
5836+
return -1;
5837+
}
5838+
annotations = PyObject_CallOneArg(
5839+
annotate, format
5840+
);
5841+
Py_DECREF(annotate);
5842+
Py_DECREF(format);
5843+
if (annotations == NULL) {
5844+
return -1;
5845+
}
5846+
}
5847+
else {
5848+
return 0; // No annotations, nothing to do
5849+
}
5850+
}
5851+
else {
5852+
Py_INCREF(annotations);
5853+
}
58215854

58225855
if (!PyDict_Check(annotations)) {
5856+
Py_DECREF(annotations);
58235857
PyErr_SetString(PyExc_TypeError, "__annotations__ must be a dict");
58245858
return -1;
58255859
}
@@ -5869,6 +5903,7 @@ structmeta_collect_fields(StructMetaInfo *info, MsgspecState *mod, bool kwonly)
58695903
}
58705904
return 0;
58715905
error:
5906+
Py_DECREF(annotations);
58725907
Py_XDECREF(module_ns);
58735908
return -1;
58745909
}
@@ -22225,6 +22260,26 @@ PyInit__core(void)
2222522260
Py_DECREF(temp_module);
2222622261
if (st->re_compile == NULL) return NULL;
2222722262

22263+
/* annotationlib.get_annotate_from_class_namespace */
22264+
temp_module = PyImport_ImportModule("annotationlib");
22265+
if (temp_module == NULL) {
22266+
if (PyErr_ExceptionMatches(PyExc_ModuleNotFoundError)) {
22267+
// Below Python 3.14
22268+
PyErr_Clear();
22269+
st->get_annotate_from_class_namespace = NULL;
22270+
}
22271+
else {
22272+
return NULL;
22273+
}
22274+
}
22275+
else {
22276+
st->get_annotate_from_class_namespace = PyObject_GetAttrString(
22277+
temp_module, "get_annotate_from_class_namespace"
22278+
);
22279+
Py_DECREF(temp_module);
22280+
if (st->get_annotate_from_class_namespace == NULL) return NULL;
22281+
}
22282+
2222822283
/* Initialize cached constant strings */
2222922284
#define CACHED_STRING(attr, str) \
2223022285
if ((st->attr = PyUnicode_InternFromString(str)) == NULL) return NULL

msgspec/_utils.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# type: ignore
22
import collections
3+
import inspect
34
import sys
45
import typing
56

@@ -71,6 +72,13 @@ def _eval_type(t, globalns, localns):
7172
_eval_type = typing._eval_type
7273

7374

75+
if sys.version_info >= (3, 10):
76+
from inspect import get_annotations as _get_class_annotations
77+
else:
78+
def _get_class_annotations(cls):
79+
return cls.__dict__.get("__annotations__", {})
80+
81+
7482
def _apply_params(obj, mapping):
7583
if isinstance(obj, typing.TypeVar):
7684
return mapping.get(obj, obj)
@@ -149,17 +157,17 @@ def get_class_annotations(obj):
149157
cls_locals = dict(vars(cls))
150158
cls_globals = getattr(sys.modules.get(cls.__module__, None), "__dict__", {})
151159

152-
ann = cls.__dict__.get("__annotations__", {})
160+
ann = _get_class_annotations(cls)
153161
for name, value in ann.items():
154162
if name in hints:
155163
continue
156-
if value is None:
157-
value = type(None)
158-
elif isinstance(value, str):
164+
if isinstance(value, str):
159165
value = _forward_ref(value)
160166
value = _eval_type(value, cls_locals, cls_globals)
161167
if mapping is not None:
162168
value = _apply_params(value, mapping)
169+
if value is None:
170+
value = type(None)
163171
hints[name] = value
164172
return hints
165173

0 commit comments

Comments
 (0)