File: py314-annotations.patch

package info (click to toggle)
python-msgspec 0.19.0-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 7,592 kB
  • sloc: javascript: 23,944; ansic: 20,592; python: 20,470; makefile: 29; sh: 19
file content (162 lines) | stat: -rw-r--r-- 5,259 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
From: Jelle Zijlstra <jelle.zijlstra@gmail.com>
Date: Sun, 26 Oct 2025 00:00:51 +0100
Subject: Fix annotations support on 3.14

With this change, the tests run for me on a local build of Python 3.14.
There are a lot of failures related to sys.getrefcount() but that seems
to be an unrelated issue.

Closes #810. Fixes #651. Fixes #795.

Origin: upstream, https://github.com/jcrist/msgspec/pull/852
Bug: https://github.com/jcrist/msgspec/issues/651
Bug: https://github.com/jcrist/msgspec/issues/795
Bug-Debian: https://bugs.debian.org/1117910
Last-Update: 2025-10-26
---
 msgspec/_core.c   | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++--
 msgspec/_utils.py | 16 +++++++++++----
 2 files changed, 69 insertions(+), 6 deletions(-)

diff --git a/msgspec/_core.c b/msgspec/_core.c
index bfb7368..b0e9b20 100644
--- a/msgspec/_core.c
+++ b/msgspec/_core.c
@@ -452,6 +452,7 @@ typedef struct {
 #endif
     PyObject *astimezone;
     PyObject *re_compile;
+    PyObject *get_annotate_from_class_namespace;
     uint8_t gc_cycle;
 } MsgspecState;
 
@@ -5814,12 +5815,45 @@ structmeta_is_classvar(
 
 static int
 structmeta_collect_fields(StructMetaInfo *info, MsgspecState *mod, bool kwonly) {
-    PyObject *annotations = PyDict_GetItemString(
+    PyObject *annotations = PyDict_GetItemString(  // borrowed reference
         info->namespace, "__annotations__"
     );
-    if (annotations == NULL) return 0;
+    if (annotations == NULL) {
+        if (mod->get_annotate_from_class_namespace != NULL) {
+            PyObject *annotate = PyObject_CallOneArg(
+                mod->get_annotate_from_class_namespace, info->namespace
+            );
+            if (annotate == NULL) {
+                return -1;
+            }
+            if (annotate == Py_None) {
+                Py_DECREF(annotate);
+                return 0;
+            }
+            PyObject *format = PyLong_FromLong(1);  /* annotationlib.Format.VALUE */
+            if (format == NULL) {
+                Py_DECREF(annotate);
+                return -1;
+            }
+            annotations = PyObject_CallOneArg(
+                annotate, format
+            );
+            Py_DECREF(annotate);
+            Py_DECREF(format);
+            if (annotations == NULL) {
+                return -1;
+            }
+        }
+        else {
+            return 0;  // No annotations, nothing to do
+        }
+    }
+    else {
+        Py_INCREF(annotations);
+    }
 
     if (!PyDict_Check(annotations)) {
+        Py_DECREF(annotations);
         PyErr_SetString(PyExc_TypeError, "__annotations__ must be a dict");
         return -1;
     }
@@ -5869,6 +5903,7 @@ structmeta_collect_fields(StructMetaInfo *info, MsgspecState *mod, bool kwonly)
     }
     return 0;
 error:
+    Py_DECREF(annotations);
     Py_XDECREF(module_ns);
     return -1;
 }
@@ -22223,6 +22258,26 @@ PyInit__core(void)
     Py_DECREF(temp_module);
     if (st->re_compile == NULL) return NULL;
 
+    /* annotationlib.get_annotate_from_class_namespace */
+    temp_module = PyImport_ImportModule("annotationlib");
+    if (temp_module == NULL) {
+        if (PyErr_ExceptionMatches(PyExc_ModuleNotFoundError)) {
+            // Below Python 3.14
+            PyErr_Clear();
+            st->get_annotate_from_class_namespace = NULL;
+        }
+        else {
+            return NULL;
+        }
+    }
+    else {
+        st->get_annotate_from_class_namespace = PyObject_GetAttrString(
+            temp_module, "get_annotate_from_class_namespace"
+        );
+        Py_DECREF(temp_module);
+        if (st->get_annotate_from_class_namespace == NULL) return NULL;
+    }
+
     /* Initialize cached constant strings */
 #define CACHED_STRING(attr, str) \
     if ((st->attr = PyUnicode_InternFromString(str)) == NULL) return NULL
diff --git a/msgspec/_utils.py b/msgspec/_utils.py
index 6d33810..534d17f 100644
--- a/msgspec/_utils.py
+++ b/msgspec/_utils.py
@@ -1,5 +1,6 @@
 # type: ignore
 import collections
+import inspect
 import sys
 import typing
 
@@ -71,6 +72,13 @@ else:
     _eval_type = typing._eval_type
 
 
+if sys.version_info >= (3, 10):
+    from inspect import get_annotations as _get_class_annotations
+else:
+    def _get_class_annotations(cls):
+        return cls.__dict__.get("__annotations__", {})
+
+
 def _apply_params(obj, mapping):
     if isinstance(obj, typing.TypeVar):
         return mapping.get(obj, obj)
@@ -149,17 +157,17 @@ def get_class_annotations(obj):
         cls_locals = dict(vars(cls))
         cls_globals = getattr(sys.modules.get(cls.__module__, None), "__dict__", {})
 
-        ann = cls.__dict__.get("__annotations__", {})
+        ann = _get_class_annotations(cls)
         for name, value in ann.items():
             if name in hints:
                 continue
-            if value is None:
-                value = type(None)
-            elif isinstance(value, str):
+            if isinstance(value, str):
                 value = _forward_ref(value)
             value = _eval_type(value, cls_locals, cls_globals)
             if mapping is not None:
                 value = _apply_params(value, mapping)
+            if value is None:
+                value = type(None)
             hints[name] = value
     return hints