@@ -4943,8 +4943,8 @@ typenode_collect_type(TypeNodeCollectState *state, PyObject *obj) {
49434943 out = typenode_collect_typevar(state, t);
49444944 }
49454945 else if (
4946- Py_TYPE(t) == &StructMetaType ||
4947- (origin != NULL && Py_TYPE(origin) == &StructMetaType)
4946+ PyType_IsSubtype( Py_TYPE(t), &StructMetaType) ||
4947+ (origin != NULL && PyType_IsSubtype( Py_TYPE(origin), &StructMetaType) )
49484948 ) {
49494949 out = typenode_collect_struct(state, t);
49504950 }
@@ -5598,7 +5598,7 @@ structmeta_collect_base(StructMetaInfo *info, MsgspecState *mod, PyObject *base)
55985598 return -1;
55995599 }
56005600
5601- if (Py_TYPE(base) != &StructMetaType) {
5601+ if (!PyType_IsSubtype( Py_TYPE(base), &StructMetaType) ) {
56025602 if (((PyTypeObject *)base)->tp_dictoffset) {
56035603 info->has_non_slots_bases = true;
56045604 }
@@ -5805,7 +5805,7 @@ structmeta_process_default(StructMetaInfo *info, PyObject *name) {
58055805 if (default_val == NULL) return -1;
58065806 }
58075807 else if (
5808- (Py_TYPE(type) == &StructMetaType) &&
5808+ (PyType_IsSubtype( Py_TYPE(type), &StructMetaType) ) &&
58095809 ((StructMetaObject *)type)->frozen != OPT_TRUE
58105810 ) {
58115811 goto error_mutable_struct;
@@ -6800,7 +6800,7 @@ StructInfo_Convert_lock_held(PyObject *obj) {
68006800 PyObject *annotations = NULL;
68016801 StructInfo *info = NULL;
68026802 bool cache_set = false;
6803- bool is_struct = Py_TYPE(obj) == &StructMetaType;
6803+ bool is_struct = PyType_IsSubtype( Py_TYPE(obj), &StructMetaType) ;
68046804
68056805 /* Check for a cached StructInfo, and return if one exists */
68066806 if (MS_LIKELY(is_struct)) {
@@ -6818,7 +6818,7 @@ StructInfo_Convert_lock_held(PyObject *obj) {
68186818 }
68196819 PyObject *origin = PyObject_GetAttr(obj, mod->str___origin__);
68206820 if (origin == NULL) return NULL;
6821- if (Py_TYPE(origin) != &StructMetaType) {
6821+ if (!PyType_IsSubtype( Py_TYPE(origin), &StructMetaType) ) {
68226822 Py_DECREF(origin);
68236823 PyErr_SetString(
68246824 PyExc_RuntimeError, "Expected __origin__ to be a Struct type"
@@ -7271,12 +7271,44 @@ static PyGetSetDef StructMeta_getset[] = {
72717271 {NULL},
72727272};
72737273
7274+ PyDoc_STRVAR(StructMeta__doc__,
7275+ "StructMeta(name, bases, namespace, /, *, **struct_config)\n"
7276+ "--\n"
7277+ "\n"
7278+ "The metaclass for creating `Struct` types. See its documentation for the\n"
7279+ "available configuration options when subclassing.\n"
7280+ "\n"
7281+ "Examples\n"
7282+ "--------\n"
7283+ "Here we define a metaclass that modifies the default configuration and use\n"
7284+ "it to create a new `Struct` base class.\n"
7285+ "\n"
7286+ ">>> from msgspec import Struct, StructMeta\n"
7287+ ">>> class KwOnlyStructMeta(StructMeta):\n"
7288+ "... def __new__(mcls, name, bases, namespace, **struct_config):\n"
7289+ "... struct_config.setdefault(\"kw_only\", True)\n"
7290+ "... return super().__new__(mcls, name, bases, namespace, **struct_config)\n"
7291+ "...\n"
7292+ ">>> class KwOnlyStruct(Struct, metaclass=KwOnlyStructMeta): ...\n"
7293+ "\n"
7294+ "Any subclass of ``KwOnlyStruct`` will have ``kw_only`` set to ``True`` by\n"
7295+ "default.\n"
7296+ "\n"
7297+ ">>> class Example(KwOnlyStruct):\n"
7298+ "... a: str = ""\n"
7299+ "... b: int\n"
7300+ "...\n"
7301+ ">>> Example(b=123)\n"
7302+ "Example(a='', b=123)\n"
7303+ );
7304+
72747305static PyTypeObject StructMetaType = {
72757306 PyVarObject_HEAD_INIT(NULL, 0)
72767307 .tp_name = "msgspec._core.StructMeta",
7308+ .tp_doc = StructMeta__doc__,
72777309 .tp_basicsize = sizeof(StructMetaObject),
72787310 .tp_itemsize = 0,
7279- .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_TYPE_SUBCLASS | Py_TPFLAGS_HAVE_GC | _Py_TPFLAGS_HAVE_VECTORCALL,
7311+ .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_TYPE_SUBCLASS | Py_TPFLAGS_HAVE_GC | _Py_TPFLAGS_HAVE_VECTORCALL | Py_TPFLAGS_BASETYPE ,
72807312 .tp_new = StructMeta_new,
72817313 .tp_dealloc = (destructor) StructMeta_dealloc,
72827314 .tp_clear = (inquiry) StructMeta_clear,
@@ -7915,7 +7947,7 @@ struct_replace(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject
79157947
79167948 if (!check_positional_nargs(nargs, 1, 1)) return NULL;
79177949 PyObject *obj = args[0];
7918- if (Py_TYPE(Py_TYPE(obj)) != &StructMetaType) {
7950+ if (!PyType_IsSubtype( Py_TYPE(Py_TYPE(obj)), &StructMetaType) ) {
79197951 PyErr_SetString(PyExc_TypeError, "`struct` must be a `msgspec.Struct`");
79207952 return NULL;
79217953 }
@@ -7956,7 +7988,7 @@ struct_asdict(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
79567988{
79577989 if (!check_positional_nargs(nargs, 1, 1)) return NULL;
79587990 PyObject *obj = args[0];
7959- if (Py_TYPE(Py_TYPE(obj)) != &StructMetaType) {
7991+ if (!PyType_IsSubtype( Py_TYPE(Py_TYPE(obj)), &StructMetaType) ) {
79607992 PyErr_SetString(PyExc_TypeError, "`struct` must be a `msgspec.Struct`");
79617993 return NULL;
79627994 }
@@ -8014,7 +8046,7 @@ struct_astuple(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
80148046{
80158047 if (!check_positional_nargs(nargs, 1, 1)) return NULL;
80168048 PyObject *obj = args[0];
8017- if (Py_TYPE(Py_TYPE(obj)) != &StructMetaType) {
8049+ if (!PyType_IsSubtype( Py_TYPE(Py_TYPE(obj)), &StructMetaType) ) {
80188050 PyErr_SetString(PyExc_TypeError, "`struct` must be a `msgspec.Struct`");
80198051 return NULL;
80208052 }
@@ -8066,7 +8098,7 @@ struct_force_setattr(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
80668098 PyObject *obj = args[0];
80678099 PyObject *name = args[1];
80688100 PyObject *value = args[2];
8069- if (Py_TYPE(Py_TYPE(obj)) != &StructMetaType) {
8101+ if (!PyType_IsSubtype( Py_TYPE(Py_TYPE(obj)), &StructMetaType) ) {
80708102 PyErr_SetString(PyExc_TypeError, "`struct` must be a `msgspec.Struct`");
80718103 return NULL;
80728104 }
@@ -13245,7 +13277,7 @@ mpack_encode_uncommon(EncoderState *self, PyTypeObject *type, PyObject *obj)
1324513277 else if (type == &PyBool_Type) {
1324613278 return mpack_encode_bool(self, obj);
1324713279 }
13248- else if (Py_TYPE(type) == &StructMetaType) {
13280+ else if (PyType_IsSubtype( Py_TYPE(type), &StructMetaType) ) {
1324913281 return mpack_encode_struct(self, obj);
1325013282 }
1325113283 else if (type == &PyBytes_Type) {
@@ -14352,7 +14384,7 @@ json_encode_uncommon(EncoderState *self, PyTypeObject *type, PyObject *obj) {
1435214384 else if (obj == Py_False) {
1435314385 return ms_write(self, "false", 5);
1435414386 }
14355- else if (Py_TYPE(type) == &StructMetaType) {
14387+ else if (PyType_IsSubtype( Py_TYPE(type), &StructMetaType) ) {
1435614388 return json_encode_struct(self, obj);
1435714389 }
1435814390 else if (PyTuple_Check(obj)) {
@@ -16481,7 +16513,7 @@ msgspec_msgpack_decode(PyObject *self, PyObject *const *args, Py_ssize_t nargs,
1648116513 if (type == NULL || type == mod->typing_any) {
1648216514 state.type = &typenode_any;
1648316515 }
16484- else if (Py_TYPE(type) == &StructMetaType) {
16516+ else if (PyType_IsSubtype( Py_TYPE(type), &StructMetaType) ) {
1648516517 PyObject *info = StructInfo_Convert(type);
1648616518 if (info == NULL) return NULL;
1648716519 bool array_like = ((StructMetaObject *)type)->array_like == OPT_TRUE;
@@ -19516,7 +19548,7 @@ msgspec_json_decode(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyO
1951619548 if (type == NULL || type == mod->typing_any) {
1951719549 state.type = &typenode_any;
1951819550 }
19519- else if (Py_TYPE(type) == &StructMetaType) {
19551+ else if (PyType_IsSubtype( Py_TYPE(type), &StructMetaType) ) {
1952019552 PyObject *info = StructInfo_Convert(type);
1952119553 if (info == NULL) return NULL;
1952219554 bool array_like = ((StructMetaObject *)type)->array_like == OPT_TRUE;
@@ -20084,7 +20116,7 @@ to_builtins(ToBuiltinsState *self, PyObject *obj, bool is_key) {
2008420116 else if (PyDict_Check(obj)) {
2008520117 return to_builtins_dict(self, obj);
2008620118 }
20087- else if (Py_TYPE(type) == &StructMetaType) {
20119+ else if (PyType_IsSubtype( Py_TYPE(type), &StructMetaType) ) {
2008820120 return to_builtins_struct(self, obj, is_key);
2008920121 }
2009020122 else if (Py_TYPE(type) == self->mod->EnumMetaType) {
@@ -21983,7 +22015,7 @@ msgspec_convert(PyObject *self, PyObject *args, PyObject *kwargs)
2198322015 state.dec_hook = dec_hook;
2198422016
2198522017 /* Avoid allocating a new TypeNode for struct types */
21986- if (Py_TYPE(pytype) == &StructMetaType) {
22018+ if (PyType_IsSubtype( Py_TYPE(pytype), &StructMetaType) ) {
2198722019 PyObject *info = StructInfo_Convert(pytype);
2198822020 if (info == NULL) return NULL;
2198922021 bool array_like = ((StructMetaObject *)pytype)->array_like == OPT_TRUE;
@@ -22306,6 +22338,12 @@ PyInit__core(void)
2230622338 Py_INCREF(&Unset_Type);
2230722339 if (PyModule_AddObject(m, "UnsetType", (PyObject *)&Unset_Type) < 0)
2230822340 return NULL;
22341+ Py_INCREF((PyObject *)&StructMetaType);
22342+ if (PyModule_AddObject(m, "StructMeta", (PyObject *)&StructMetaType) < 0) {
22343+ Py_DECREF((PyObject *)&StructMetaType);
22344+ Py_DECREF(m);
22345+ return NULL;
22346+ }
2230922347
2231022348 st = msgspec_get_state(m);
2231122349
0 commit comments