structtype 0.10.0__tar.gz → 0.11.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. {structtype-0.10.0 → structtype-0.11.0}/Makefile +2 -2
  2. {structtype-0.10.0/src/structtype.egg-info → structtype-0.11.0}/PKG-INFO +1 -1
  3. {structtype-0.10.0 → structtype-0.11.0}/pyproject.toml +4 -1
  4. {structtype-0.10.0 → structtype-0.11.0}/src/structtype/__init__.py +0 -2
  5. {structtype-0.10.0 → structtype-0.11.0}/src/structtype/__init__.pyi +1 -11
  6. {structtype-0.10.0 → structtype-0.11.0}/src/structtype/_adapter.py +1 -5
  7. {structtype-0.10.0 → structtype-0.11.0}/src/structtype/_core.c +75 -358
  8. {structtype-0.10.0 → structtype-0.11.0}/src/structtype/_inspect.py +18 -22
  9. {structtype-0.10.0 → structtype-0.11.0}/src/structtype/_json_schema.py +1 -2
  10. {structtype-0.10.0 → structtype-0.11.0}/src/structtype/_version.py +2 -2
  11. {structtype-0.10.0 → structtype-0.11.0/src/structtype.egg-info}/PKG-INFO +1 -1
  12. {structtype-0.10.0 → structtype-0.11.0}/src/structtype.egg-info/SOURCES.txt +1 -1
  13. {structtype-0.10.0 → structtype-0.11.0}/tests/test_annotations.py +1 -2
  14. {structtype-0.10.0 → structtype-0.11.0}/tests/test_check.py +0 -1
  15. {structtype-0.10.0 → structtype-0.11.0}/tests/test_inspect.py +0 -4
  16. {structtype-0.10.0 → structtype-0.11.0}/tests/test_json.py +0 -71
  17. structtype-0.11.0/tests/test_roundtrip.py +220 -0
  18. {structtype-0.10.0 → structtype-0.11.0}/tests/test_schema.py +0 -4
  19. {structtype-0.10.0 → structtype-0.11.0}/tests/typecheck/types.py +1 -6
  20. structtype-0.10.0/tests/test_raw.py +0 -138
  21. {structtype-0.10.0 → structtype-0.11.0}/.opencode/opencode.json +0 -0
  22. {structtype-0.10.0 → structtype-0.11.0}/.opencode/plugins/graphify.js +0 -0
  23. {structtype-0.10.0 → structtype-0.11.0}/AGENTS.md +0 -0
  24. {structtype-0.10.0 → structtype-0.11.0}/LICENSE +0 -0
  25. {structtype-0.10.0 → structtype-0.11.0}/MANIFEST.in +0 -0
  26. {structtype-0.10.0 → structtype-0.11.0}/README.md +0 -0
  27. {structtype-0.10.0 → structtype-0.11.0}/setup.cfg +0 -0
  28. {structtype-0.10.0 → structtype-0.11.0}/setup.py +0 -0
  29. {structtype-0.10.0 → structtype-0.11.0}/src/structtype/_config.py +0 -0
  30. {structtype-0.10.0 → structtype-0.11.0}/src/structtype/_utils.py +0 -0
  31. {structtype-0.10.0 → structtype-0.11.0}/src/structtype/atof.h +0 -0
  32. {structtype-0.10.0 → structtype-0.11.0}/src/structtype/atof_consts.h +0 -0
  33. {structtype-0.10.0 → structtype-0.11.0}/src/structtype/common.h +0 -0
  34. {structtype-0.10.0 → structtype-0.11.0}/src/structtype/itoa.h +0 -0
  35. {structtype-0.10.0 → structtype-0.11.0}/src/structtype/py.typed +0 -0
  36. {structtype-0.10.0 → structtype-0.11.0}/src/structtype/ryu.h +0 -0
  37. {structtype-0.10.0 → structtype-0.11.0}/src/structtype.egg-info/dependency_links.txt +0 -0
  38. {structtype-0.10.0 → structtype-0.11.0}/src/structtype.egg-info/top_level.txt +0 -0
  39. {structtype-0.10.0 → structtype-0.11.0}/tests/__init__.py +0 -0
  40. {structtype-0.10.0 → structtype-0.11.0}/tests/conftest.py +0 -0
  41. {structtype-0.10.0 → structtype-0.11.0}/tests/test_JSONTestSuite.py +0 -0
  42. {structtype-0.10.0 → structtype-0.11.0}/tests/test_adapter.py +0 -0
  43. {structtype-0.10.0 → structtype-0.11.0}/tests/test_attrs.py +0 -0
  44. {structtype-0.10.0 → structtype-0.11.0}/tests/test_constraints.py +0 -0
  45. {structtype-0.10.0 → structtype-0.11.0}/tests/test_cpylint.py +0 -0
  46. {structtype-0.10.0 → structtype-0.11.0}/tests/test_free_threading.py +0 -0
  47. {structtype-0.10.0 → structtype-0.11.0}/tests/test_memory.py +0 -0
  48. {structtype-0.10.0 → structtype-0.11.0}/tests/test_msgspec.py +0 -0
  49. {structtype-0.10.0 → structtype-0.11.0}/tests/test_pydantic.py +0 -0
  50. {structtype-0.10.0 → structtype-0.11.0}/tests/test_struct.py +0 -0
  51. {structtype-0.10.0 → structtype-0.11.0}/tests/test_struct_meta.py +0 -0
  52. {structtype-0.10.0 → structtype-0.11.0}/tests/test_typecheck.py +0 -0
  53. {structtype-0.10.0 → structtype-0.11.0}/tests/test_utils.py +0 -0
  54. {structtype-0.10.0 → structtype-0.11.0}/tests/typecheck/api.py +0 -0
  55. {structtype-0.10.0 → structtype-0.11.0}/tests/typecheck/basic.py +0 -0
  56. {structtype-0.10.0 → structtype-0.11.0}/tests/typecheck/fields.py +0 -0
  57. {structtype-0.10.0 → structtype-0.11.0}/tests/utils.py +0 -0
  58. {structtype-0.10.0 → structtype-0.11.0}/uv.lock +0 -0
@@ -18,8 +18,8 @@ docs-serve: ## Open built docs in browser
18
18
  uv run python -m webbrowser site/index.html
19
19
 
20
20
  .PHONY: wheels
21
- wheels: ## build wheels
22
- uvx --from cibuildwheel==4.2.0 cibuildwheel
21
+ wheels: ## build wheels (musllinux skipped; unset CIBW_SKIP or run cibuildwheel directly to include it)
22
+ CIBW_SKIP='*-musllinux_*' uvx --from cibuildwheel==4.2.0 cibuildwheel
23
23
 
24
24
  ##@ Quality
25
25
  .PHONY: test-cov
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: structtype
3
- Version: 0.10.0
3
+ Version: 0.11.0
4
4
  Summary: Fast Struct type with validation and JSON serialization for Python.
5
5
  Maintainer-email: Wolfgang Langner <tds333@mailbox.org>
6
6
  License-Expression: BSD-3-Clause
@@ -69,12 +69,15 @@ editable_mode = "compat"
69
69
 
70
70
  [tool.cibuildwheel]
71
71
  build = "cp3*"
72
- skip = ["pp*", "*-manylinux_i686", "*-musllinux*", "*-win32"]
72
+ skip = ["pp*", "*-manylinux_i686", "*-win32"]
73
73
  test-command = "pip install pytest>=9 && pytest {project}/tests"
74
74
 
75
75
  [tool.ruff]
76
76
  target-version = "py310"
77
77
 
78
+ [tool.ruff.lint.isort]
79
+ combine-as-imports = true
80
+
78
81
  [tool.coverage.report]
79
82
  exclude_lines = [
80
83
  'pragma: no cover',
@@ -12,7 +12,6 @@ from ._core import ( # type: ignore
12
12
  Factory,
13
13
  Field,
14
14
  NumericConstraint,
15
- Raw,
16
15
  Serializer,
17
16
  StrConstraint,
18
17
  Struct,
@@ -38,7 +37,6 @@ __all__ = [
38
37
  "Field",
39
38
  "FieldInfo",
40
39
  "NumericConstraint",
41
- "Raw",
42
40
  "Serializer",
43
41
  "StrConstraint",
44
42
  "Struct",
@@ -1,4 +1,4 @@
1
- # ruff: noqa: PYI041, PYI015, PYI020, UP037
1
+ # ruff: noqa: PYI041, PYI015
2
2
  import enum
3
3
  from collections.abc import Callable, Iterable, Iterator, Mapping
4
4
  from inspect import Signature
@@ -109,16 +109,6 @@ class Struct(metaclass=StructMeta):
109
109
  from_attributes: bool = False,
110
110
  ) -> Self: ...
111
111
 
112
- # Lie and say `Raw` is a subclass of `bytes`, so mypy will accept it in most
113
- # places where an object that implements the buffer protocol is valid
114
- @final
115
- class Raw(bytes):
116
- @overload
117
- def __new__(cls) -> "Raw": ...
118
- @overload
119
- def __new__(cls, msg: Buffer | str) -> "Raw": ...
120
- def copy(self) -> "Raw": ...
121
-
122
112
  #: We can't represent this in types, only via a name:
123
113
  _NonNegativeInt: TypeAlias = int
124
114
 
@@ -1,12 +1,8 @@
1
1
  from typing import Any, get_args
2
2
 
3
- from ._core import (
3
+ from ._core import ( # type: ignore
4
4
  JSONDecoder as _JSONDecoder,
5
- )
6
- from ._core import (
7
5
  Serializer as _Serializer,
8
- )
9
- from ._core import ( # type: ignore
10
6
  _dump,
11
7
  _json_encode,
12
8
  _validate,
@@ -1417,242 +1417,6 @@ static PyTypeObject StrLookup_Type = {
1417
1417
  .tp_traverse = (traverseproc) StrLookup_traverse,
1418
1418
  };
1419
1419
 
1420
- /*************************************************************************
1421
- * Raw *
1422
- *************************************************************************/
1423
-
1424
- static PyTypeObject Raw_Type;
1425
-
1426
- typedef struct Raw {
1427
- PyObject_HEAD
1428
- PyObject *base;
1429
- char *buf;
1430
- Py_ssize_t len;
1431
- bool is_view;
1432
- Py_buffer view;
1433
- } Raw;
1434
-
1435
- static PyObject *
1436
- Raw_New(PyObject *msg) {
1437
- Raw *out = (Raw *)Raw_Type.tp_alloc(&Raw_Type, 0);
1438
- if (out == NULL) return NULL;
1439
- if (PyBytes_CheckExact(msg)) {
1440
- Py_INCREF(msg);
1441
- out->base = msg;
1442
- out->buf = PyBytes_AS_STRING(msg);
1443
- out->len = PyBytes_GET_SIZE(msg);
1444
- out->is_view = false;
1445
- }
1446
- else if (PyUnicode_CheckExact(msg)) {
1447
- out->base = msg;
1448
- out->buf = (char *)unicode_str_and_size(msg, &out->len);
1449
- if (out->buf == NULL) return NULL;
1450
- Py_INCREF(msg);
1451
- out->is_view = false;
1452
- }
1453
- else {
1454
- Py_buffer buffer;
1455
- if (PyObject_GetBuffer(msg, &buffer, PyBUF_CONTIG_RO) < 0) {
1456
- Py_DECREF(out);
1457
- return NULL;
1458
- }
1459
- out->view = buffer;
1460
- out->base = buffer.obj;
1461
- out->buf = buffer.buf;
1462
- out->len = buffer.len;
1463
- out->is_view = true;
1464
- }
1465
- return (PyObject *)out;
1466
- }
1467
-
1468
- PyDoc_STRVAR(Raw__doc__,
1469
- "Raw(msg="", /)\n"
1470
- "--\n"
1471
- "\n"
1472
- "A buffer containing an encoded message.\n"
1473
- "\n"
1474
- "Raw objects have two common uses:\n"
1475
- "\n"
1476
- "- During decoding. Fields annotated with the ``Raw`` type won't be decoded\n"
1477
- " immediately, but will instead return a ``Raw`` object with a view into the\n"
1478
- " original message where that field is encoded. This is useful for decoding\n"
1479
- " fields whose type may only be inferred after decoding other fields.\n"
1480
- "- During encoding. Raw objects wrap pre-encoded messages. These can be added\n"
1481
- " as components of larger messages without having to pay the cost of decoding\n"
1482
- " and re-encoding them.\n"
1483
- "\n"
1484
- "Parameters\n"
1485
- "----------\n"
1486
- "msg: bytes, bytearray, memoryview, or str, optional\n"
1487
- " A buffer containing an encoded message. One of bytes, bytearray, memoryview,\n"
1488
- " str, or any object that implements the buffer protocol. If not present,\n"
1489
- " defaults to an empty buffer."
1490
- );
1491
- static PyObject *
1492
- Raw_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) {
1493
- PyObject *msg;
1494
- Py_ssize_t nargs, nkwargs;
1495
-
1496
- nargs = PyTuple_GET_SIZE(args);
1497
- nkwargs = (kwargs == NULL) ? 0 : PyDict_GET_SIZE(kwargs);
1498
-
1499
- if (nkwargs != 0) {
1500
- PyErr_SetString(
1501
- PyExc_TypeError,
1502
- "Raw takes no keyword arguments"
1503
- );
1504
- return NULL;
1505
- }
1506
- else if (nargs == 0) {
1507
- msg = PyBytes_FromStringAndSize(NULL, 0);
1508
- if (msg == NULL) return NULL;
1509
- /* This looks weird, but is safe since the empty bytes object is an
1510
- * immortal singleton */
1511
- Py_DECREF(msg);
1512
- }
1513
- else if (nargs == 1) {
1514
- msg = PyTuple_GET_ITEM(args, 0);
1515
- }
1516
- else {
1517
- PyErr_Format(
1518
- PyExc_TypeError,
1519
- "Raw expected at most 1 arguments, got %zd",
1520
- nargs
1521
- );
1522
- return NULL;
1523
- }
1524
- return Raw_New(msg);
1525
- }
1526
-
1527
- static void
1528
- Raw_dealloc(Raw *self)
1529
- {
1530
- if (self->base != NULL) {
1531
- if (!self->is_view) {
1532
- Py_DECREF(self->base);
1533
- }
1534
- else {
1535
- ms_release_buffer(&self->view);
1536
- }
1537
- }
1538
- Py_TYPE(self)->tp_free((PyObject *)self);
1539
- }
1540
-
1541
- static PyObject *
1542
- Raw_FromView(PyObject *buffer_obj, char *data, Py_ssize_t len) {
1543
- Raw *out = (Raw *)Raw_Type.tp_alloc(&Raw_Type, 0);
1544
- if (out == NULL) return NULL;
1545
-
1546
- Py_buffer buffer;
1547
- if (ms_get_buffer(buffer_obj, &buffer) < 0) {
1548
- Py_DECREF(out);
1549
- return NULL;
1550
- }
1551
- out->view = buffer;
1552
- out->base = buffer.obj;
1553
- out->buf = data;
1554
- out->len = len;
1555
- out->is_view = true;
1556
- return (PyObject *)out;
1557
- }
1558
-
1559
- static PyObject *
1560
- Raw_richcompare(Raw *self, PyObject *other, int op) {
1561
- if (Py_TYPE(other) != &Raw_Type) {
1562
- Py_RETURN_NOTIMPLEMENTED;
1563
- }
1564
- if (op != Py_EQ && op != Py_NE) {
1565
- Py_RETURN_NOTIMPLEMENTED;
1566
- }
1567
-
1568
- Raw *raw_other = (Raw *)other;
1569
- bool equal = (
1570
- self == raw_other || (
1571
- (self->len == raw_other->len) &&
1572
- (memcmp(self->buf, raw_other->buf, self->len) == 0)
1573
- )
1574
- );
1575
- bool result = (op == Py_EQ) ? equal : !equal;
1576
- if (result) {
1577
- Py_RETURN_TRUE;
1578
- }
1579
- Py_RETURN_FALSE;
1580
- }
1581
-
1582
- static int
1583
- Raw_buffer_getbuffer(Raw *self, Py_buffer *view, int flags)
1584
- {
1585
- return PyBuffer_FillInfo(view, (PyObject *)self, self->buf, self->len, 1, flags);
1586
- }
1587
-
1588
- static PyBufferProcs Raw_as_buffer = {
1589
- .bf_getbuffer = (getbufferproc)Raw_buffer_getbuffer
1590
- };
1591
-
1592
- static Py_ssize_t
1593
- Raw_length(Raw *self) {
1594
- return self->len;
1595
- }
1596
-
1597
- static PySequenceMethods Raw_as_sequence = {
1598
- .sq_length = (lenfunc)Raw_length
1599
- };
1600
-
1601
- static PyObject *
1602
- Raw_reduce(Raw *self, PyObject *unused)
1603
- {
1604
- if (!self->is_view) {
1605
- return Py_BuildValue("O(O)", &Raw_Type, self->base);
1606
- }
1607
- return Py_BuildValue("O(y#)", &Raw_Type, self->buf, self->len);
1608
- }
1609
-
1610
- PyDoc_STRVAR(Raw_copy__doc__,
1611
- "copy(self)\n"
1612
- "--\n"
1613
- "\n"
1614
- "Copy a Raw object.\n"
1615
- "\n"
1616
- "If the raw message is backed by a memoryview into a larger buffer (as happens\n"
1617
- "during decoding), the message is copied and the reference to the larger buffer\n"
1618
- "released. This may be useful to reduce memory usage if a Raw object created\n"
1619
- "during decoding will be kept in memory for a while rather than immediately\n"
1620
- "decoded and dropped."
1621
- );
1622
- static PyObject *
1623
- Raw_copy(Raw *self, PyObject *unused)
1624
- {
1625
- if (!self->is_view) {
1626
- Py_INCREF(self);
1627
- return (PyObject *)self;
1628
- }
1629
- PyObject *buf = PyBytes_FromStringAndSize(self->buf, self->len);
1630
- if (buf == NULL) return NULL;
1631
- PyObject *out = Raw_New(buf);
1632
- Py_DECREF(buf);
1633
- return out;
1634
- }
1635
-
1636
- static PyMethodDef Raw_methods[] = {
1637
- {"__reduce__", (PyCFunction)Raw_reduce, METH_NOARGS},
1638
- {"copy", (PyCFunction)Raw_copy, METH_NOARGS, Raw_copy__doc__},
1639
- {NULL, NULL},
1640
- };
1641
-
1642
- static PyTypeObject Raw_Type = {
1643
- PyVarObject_HEAD_INIT(NULL, 0)
1644
- .tp_name = "structtype.Raw",
1645
- .tp_doc = Raw__doc__,
1646
- .tp_basicsize = sizeof(Raw),
1647
- .tp_itemsize = sizeof(char),
1648
- .tp_flags = Py_TPFLAGS_DEFAULT,
1649
- .tp_new = Raw_new,
1650
- .tp_dealloc = (destructor) Raw_dealloc,
1651
- .tp_as_buffer = &Raw_as_buffer,
1652
- .tp_as_sequence = &Raw_as_sequence,
1653
- .tp_methods = Raw_methods,
1654
- .tp_richcompare = (richcmpfunc) Raw_richcompare,
1655
- };
1656
1420
 
1657
1421
  static bool
1658
1422
  ensure_is_string(PyObject *val, const char *param) {
@@ -6855,9 +6619,6 @@ typenode_collect_type(TypeNodeCollectState *state, PyObject *obj) {
6855
6619
  else if (t == state->mod->DecimalType) {
6856
6620
  state->types |= MS_TYPE_DECIMAL;
6857
6621
  }
6858
- else if (t == (PyObject *)(&Raw_Type)) {
6859
- /* Raw is marked with a typecode of 0, nothing to do */
6860
- }
6861
6622
  else if (Py_TYPE(t) == (PyTypeObject *)(state->mod->typing_typevar)) {
6862
6623
  out = typenode_collect_typevar(state, t);
6863
6624
  }
@@ -10380,7 +10141,7 @@ PyDoc_STRVAR(Struct_dump__doc__,
10380
10141
  "struct_dump(self, *, sort_keys=False, str_keys=False, builtin_types=None)\n"
10381
10142
  "--\n"
10382
10143
  "\n"
10383
- "Convert this struct to built-in Python types.\n"
10144
+ "Convert this struct to built-in Python types compatible with JSON basic types.\n"
10384
10145
  "\n"
10385
10146
  "This applies the same semantics as ``struct_dump_json`` but produces\n"
10386
10147
  "Python objects instead of JSON bytes:\n"
@@ -10430,8 +10191,7 @@ PyDoc_STRVAR(Struct_validate__doc__,
10430
10191
  "strict : bool, optional\n"
10431
10192
  " Whether type coercion rules should be strict. If ``False``, enables a\n"
10432
10193
  " wider set of coercion rules from string to non-string types for all\n"
10433
- " values. Setting ``strict=False`` implies ``str_keys=True`` and\n"
10434
- " ``builtin_types=None``. Default is ``True``.\n"
10194
+ " values, including dict keys. Default is ``True``.\n"
10435
10195
  "from_attributes : bool, optional\n"
10436
10196
  " If ``True``, input objects may be coerced to struct, dataclass, or\n"
10437
10197
  " attrs types by extracting attributes matching output field names.\n"
@@ -14491,13 +14251,6 @@ json_encode_memoryview(EncoderState *self, PyObject *obj)
14491
14251
  return out;
14492
14252
  }
14493
14253
 
14494
- static int
14495
- json_encode_raw(EncoderState *self, PyObject *obj)
14496
- {
14497
- Raw *raw = (Raw *)obj;
14498
- return ms_write(self, raw->buf, raw->len);
14499
- }
14500
-
14501
14254
  static int json_encode_dict_key_noinline(EncoderState *, PyObject *);
14502
14255
 
14503
14256
  /* Extract an enum member's value. Enum members store their value as a plain
@@ -14779,8 +14532,8 @@ json_encode_dict_key_noinline(EncoderState *self, PyObject *obj) {
14779
14532
  else if (type == &PyFloat_Type) {
14780
14533
  return json_encode_float_as_str(self, obj);
14781
14534
  }
14782
- else if (PyType_IsSubtype(Py_TYPE(type), self->mod->EnumMetaType)) {
14783
- return json_encode_enum(self, obj, true);
14535
+ else if (type == (PyTypeObject *)(self->mod->DecimalType)) {
14536
+ return json_encode_decimal(self, obj);
14784
14537
  }
14785
14538
  else if (type == PyDateTimeAPI->DateTimeType) {
14786
14539
  return json_encode_datetime(self, obj);
@@ -14794,15 +14547,15 @@ json_encode_dict_key_noinline(EncoderState *self, PyObject *obj) {
14794
14547
  else if (type == PyDateTimeAPI->DeltaType) {
14795
14548
  return json_encode_timedelta(self, obj);
14796
14549
  }
14797
- else if (type == &PyBytes_Type) {
14798
- return json_encode_bytes(self, obj);
14799
- }
14800
- else if (type == (PyTypeObject *)(self->mod->DecimalType)) {
14801
- return json_encode_decimal(self, obj);
14550
+ else if (PyType_IsSubtype(Py_TYPE(type), self->mod->EnumMetaType)) {
14551
+ return json_encode_enum(self, obj, true);
14802
14552
  }
14803
14553
  else if (PyType_IsSubtype(type, (PyTypeObject *)(self->mod->UUIDType))) {
14804
14554
  return json_encode_uuid(self, obj);
14805
14555
  }
14556
+ else if (type == &PyBytes_Type) {
14557
+ return json_encode_bytes(self, obj);
14558
+ }
14806
14559
  if (self->codecs != NULL) {
14807
14560
  PyObject *dump = codecs_lookup(self->codecs, type);
14808
14561
  if (dump != NULL) {
@@ -15160,18 +14913,12 @@ json_encode_struct(EncoderState *self, PyObject *obj)
15160
14913
 
15161
14914
  static MS_NOINLINE int
15162
14915
  json_encode_uncommon(EncoderState *self, PyTypeObject *type, PyObject *obj) {
15163
- if (obj == Py_None) {
15164
- return ms_write(self, "null", 4);
15165
- }
15166
- else if (obj == Py_True) {
15167
- return ms_write(self, "true", 4);
15168
- }
15169
- else if (obj == Py_False) {
15170
- return ms_write(self, "false", 5);
15171
- }
15172
- else if (PyTuple_Check(obj)) {
14916
+ if (PyTuple_Check(obj)) {
15173
14917
  return json_encode_tuple(self, obj);
15174
14918
  }
14919
+ else if (type == (PyTypeObject *)(self->mod->DecimalType)) {
14920
+ return json_encode_decimal(self, obj);
14921
+ }
15175
14922
  else if (type == PyDateTimeAPI->DateTimeType) {
15176
14923
  return json_encode_datetime(self, obj);
15177
14924
  }
@@ -15184,30 +14931,24 @@ json_encode_uncommon(EncoderState *self, PyTypeObject *type, PyObject *obj) {
15184
14931
  else if (type == PyDateTimeAPI->DeltaType) {
15185
14932
  return json_encode_timedelta(self, obj);
15186
14933
  }
15187
- else if (type == &PyBytes_Type) {
15188
- return json_encode_bytes(self, obj);
15189
- }
15190
- else if (type == &PyByteArray_Type) {
15191
- return json_encode_bytearray(self, obj);
15192
- }
15193
- else if (type == &PyMemoryView_Type) {
15194
- return json_encode_memoryview(self, obj);
15195
- }
15196
- else if (type == &Raw_Type) {
15197
- return json_encode_raw(self, obj);
15198
- }
15199
14934
  else if (PyType_IsSubtype(Py_TYPE(type), self->mod->EnumMetaType)) {
15200
14935
  return json_encode_enum(self, obj, false);
15201
14936
  }
15202
14937
  else if (PyType_IsSubtype(type, (PyTypeObject *)(self->mod->UUIDType))) {
15203
14938
  return json_encode_uuid(self, obj);
15204
14939
  }
15205
- else if (type == (PyTypeObject *)(self->mod->DecimalType)) {
15206
- return json_encode_decimal(self, obj);
15207
- }
15208
14940
  else if (PyAnySet_Check(obj)) {
15209
14941
  return json_encode_set(self, obj);
15210
14942
  }
14943
+ else if (type == &PyBytes_Type) {
14944
+ return json_encode_bytes(self, obj);
14945
+ }
14946
+ else if (type == &PyByteArray_Type) {
14947
+ return json_encode_bytearray(self, obj);
14948
+ }
14949
+ else if (type == &PyMemoryView_Type) {
14950
+ return json_encode_memoryview(self, obj);
14951
+ }
15211
14952
  if (self->codecs != NULL) {
15212
14953
  PyObject *dump = codecs_lookup(self->codecs, type);
15213
14954
  if (dump != NULL) {
@@ -15299,7 +15040,16 @@ json_encode_inline(EncoderState *self, PyObject *obj)
15299
15040
  {
15300
15041
  PyTypeObject *type = Py_TYPE(obj);
15301
15042
 
15302
- if (PyUnicode_Check(obj)) {
15043
+ if (obj == Py_None) {
15044
+ return ms_write(self, "null", 4);
15045
+ }
15046
+ else if (obj == Py_True) {
15047
+ return ms_write(self, "true", 4);
15048
+ }
15049
+ else if (obj == Py_False) {
15050
+ return ms_write(self, "false", 5);
15051
+ }
15052
+ else if (PyUnicode_Check(obj)) {
15303
15053
  return json_encode_str(self, obj);
15304
15054
  }
15305
15055
  else if (type == &PyLong_Type) {
@@ -17572,16 +17322,6 @@ json_maybe_decode_number(JSONDecoderState *self, TypeNode *type, PathNode *path)
17572
17322
  return out;
17573
17323
  }
17574
17324
 
17575
- static MS_NOINLINE PyObject *
17576
- json_decode_raw(JSONDecoderState *self) {
17577
- unsigned char c;
17578
- if (MS_UNLIKELY(!json_peek_skip_ws(self, &c))) return NULL;
17579
- const unsigned char *start = self->input_pos;
17580
- if (json_skip(self) < 0) return NULL;
17581
- Py_ssize_t size = self->input_pos - start;
17582
- return Raw_FromView(self->buffer_obj, (char *)start, size);
17583
- }
17584
-
17585
17325
  /* Fast path for the common `str`/`Any` string decode, inlined so the hot path
17586
17326
  * avoids the large non-inline `json_decode_string` (which also handles
17587
17327
  * datetime, uuid, decimal, bytes, enum, ...). */
@@ -17631,9 +17371,6 @@ static PyObject *
17631
17371
  json_decode(
17632
17372
  JSONDecoderState *self, TypeNode *type, PathNode *path
17633
17373
  ) {
17634
- if (MS_UNLIKELY(type->types == 0)) {
17635
- return json_decode_raw(self);
17636
- }
17637
17374
  PyObject *obj = json_decode_nocustom(self, type, path);
17638
17375
  if (MS_UNLIKELY(type->types & (MS_TYPE_CUSTOM | MS_TYPE_CUSTOM_GENERIC))) {
17639
17376
  return ms_decode_custom(obj, type, path);
@@ -18574,26 +18311,26 @@ dump_obj(DumpState *self, PyObject *obj, bool is_key) {
18574
18311
  ) {
18575
18312
  goto builtin;
18576
18313
  }
18577
- else if (type == &PyBytes_Type) {
18578
- if (self->builtin_types & MS_BUILTIN_BYTES) goto builtin;
18579
- return dump_binary(
18580
- self, PyBytes_AS_STRING(obj), PyBytes_GET_SIZE(obj)
18581
- );
18314
+ else if (PyList_Check(obj)) {
18315
+ return dump_list(self, obj);
18582
18316
  }
18583
- else if (type == &PyByteArray_Type) {
18584
- if (self->builtin_types & MS_BUILTIN_BYTEARRAY) goto builtin;
18585
- return dump_binary(
18586
- self, PyByteArray_AS_STRING(obj), PyByteArray_GET_SIZE(obj)
18587
- );
18317
+ else if (PyTuple_Check(obj)) {
18318
+ return dump_tuple(self, obj, is_key);
18588
18319
  }
18589
- else if (type == &PyMemoryView_Type) {
18590
- if (self->builtin_types & MS_BUILTIN_MEMORYVIEW) goto builtin;
18591
- PyObject *out;
18592
- Py_buffer buffer;
18593
- if (PyObject_GetBuffer(obj, &buffer, PyBUF_CONTIG_RO) < 0) return NULL;
18594
- out = dump_binary(self, buffer.buf, buffer.len);
18595
- PyBuffer_Release(&buffer);
18596
- return out;
18320
+ else if (PyDict_Check(obj)) {
18321
+ return dump_dict(self, obj);
18322
+ }
18323
+ #if PY315_PLUS
18324
+ else if (PyFrozenDict_Check(obj)) {
18325
+ return dump_frozendict(self, obj);
18326
+ }
18327
+ #endif
18328
+ else if (ms_is_struct_type(type)) {
18329
+ return dump_struct(self, obj, is_key);
18330
+ }
18331
+ else if (type == (PyTypeObject *)(self->mod->DecimalType)) {
18332
+ if (self->builtin_types & MS_BUILTIN_DECIMAL) goto builtin;
18333
+ return dump_decimal(self, obj);
18597
18334
  }
18598
18335
  else if (type == PyDateTimeAPI->DateTimeType) {
18599
18336
  if (self->builtin_types & MS_BUILTIN_DATETIME) goto builtin;
@@ -18611,38 +18348,10 @@ dump_obj(DumpState *self, PyObject *obj, bool is_key) {
18611
18348
  if (self->builtin_types & MS_BUILTIN_TIMEDELTA) goto builtin;
18612
18349
  return dump_timedelta(self, obj);
18613
18350
  }
18614
- else if (type == (PyTypeObject *)(self->mod->DecimalType)) {
18615
- if (self->builtin_types & MS_BUILTIN_DECIMAL) goto builtin;
18616
- return dump_decimal(self, obj);
18617
- }
18618
- else if (PyList_Check(obj)) {
18619
- return dump_list(self, obj);
18620
- }
18621
- else if (PyTuple_Check(obj)) {
18622
- return dump_tuple(self, obj, is_key);
18623
- }
18624
- else if (PyDict_Check(obj)) {
18625
- return dump_dict(self, obj);
18626
- }
18627
- #if PY315_PLUS
18628
- else if (PyFrozenDict_Check(obj)) {
18629
- return dump_frozendict(self, obj);
18630
- }
18631
- #endif
18632
- else if (ms_is_struct_type(type)) {
18633
- return dump_struct(self, obj, is_key);
18634
- }
18635
- else if (!ms_is_struct_inst(obj) && PyObject_HasAttr(obj, self->mod->str___struct_fields__)) {
18636
- /* External struct type (e.g. msgspec) — use Python-level attribute access */
18637
- return dump_external_struct(self, obj);
18638
- }
18639
18351
  else if (PyType_IsSubtype(Py_TYPE(type), self->mod->EnumMetaType)) {
18640
18352
  if (self->builtin_types & MS_BUILTIN_ENUM) goto builtin;
18641
18353
  return dump_enum(self, obj);
18642
18354
  }
18643
- else if (is_key & PyUnicode_Check(obj)) {
18644
- return PyObject_Str(obj);
18645
- }
18646
18355
  else if (PyType_IsSubtype(type, (PyTypeObject *)(self->mod->UUIDType))) {
18647
18356
  if (self->builtin_types & MS_BUILTIN_UUID) goto builtin;
18648
18357
  return dump_uuid(self, obj);
@@ -18650,6 +18359,27 @@ dump_obj(DumpState *self, PyObject *obj, bool is_key) {
18650
18359
  else if (PyAnySet_Check(obj)) {
18651
18360
  return dump_set(self, obj, is_key);
18652
18361
  }
18362
+ else if (type == &PyBytes_Type) {
18363
+ if (self->builtin_types & MS_BUILTIN_BYTES) goto builtin;
18364
+ return dump_binary(
18365
+ self, PyBytes_AS_STRING(obj), PyBytes_GET_SIZE(obj)
18366
+ );
18367
+ }
18368
+ else if (type == &PyByteArray_Type) {
18369
+ if (self->builtin_types & MS_BUILTIN_BYTEARRAY) goto builtin;
18370
+ return dump_binary(
18371
+ self, PyByteArray_AS_STRING(obj), PyByteArray_GET_SIZE(obj)
18372
+ );
18373
+ }
18374
+ else if (type == &PyMemoryView_Type) {
18375
+ if (self->builtin_types & MS_BUILTIN_MEMORYVIEW) goto builtin;
18376
+ PyObject *out;
18377
+ Py_buffer buffer;
18378
+ if (PyObject_GetBuffer(obj, &buffer, PyBUF_CONTIG_RO) < 0) return NULL;
18379
+ out = dump_binary(self, buffer.buf, buffer.len);
18380
+ PyBuffer_Release(&buffer);
18381
+ return out;
18382
+ }
18653
18383
  if (self->codecs != NULL) {
18654
18384
  PyObject *dump = codecs_lookup(self->codecs, type);
18655
18385
  if (dump != NULL) {
@@ -18682,6 +18412,10 @@ dump_obj(DumpState *self, PyObject *obj, bool is_key) {
18682
18412
  Py_DECREF(dict);
18683
18413
  return result;
18684
18414
  }
18415
+ else if (!ms_is_struct_inst(obj) && PyObject_HasAttr(obj, self->mod->str___struct_fields__)) {
18416
+ /* External struct type (e.g. msgspec) — use Python-level attribute access */
18417
+ return dump_external_struct(self, obj);
18418
+ }
18685
18419
  else if (!PyType_Check(obj) && type->tp_dict != NULL) {
18686
18420
  PyObject *fields = PyObject_GetAttr(obj, self->mod->str___dataclass_fields__);
18687
18421
  if (fields != NULL) {
@@ -19247,16 +18981,6 @@ validate_immutable(
19247
18981
  return ms_validation_error(expected, type, path);
19248
18982
  }
19249
18983
 
19250
- static PyObject *
19251
- validate_raw(
19252
- ValidateState *self, PyObject *obj, TypeNode *type, PathNode *path
19253
- ) {
19254
- if (type->types == 0) {
19255
- Py_INCREF(obj);
19256
- return obj;
19257
- }
19258
- return ms_validation_error("raw", type, path);
19259
- }
19260
18984
 
19261
18985
  static PyObject *
19262
18986
  validate_seq_to_list(
@@ -20403,9 +20127,6 @@ validate_obj_dispatch(
20403
20127
  else if (PyType_IsSubtype(Py_TYPE(pytype), self->mod->EnumMetaType)) {
20404
20128
  return validate_enum(self, obj, type, path);
20405
20129
  }
20406
- else if (pytype == &Raw_Type) {
20407
- return validate_raw(self, obj, type, path);
20408
- }
20409
20130
  else if (PyAnySet_Check(obj)) {
20410
20131
  return validate_any_set(self, obj, type, path);
20411
20132
  }
@@ -21084,8 +20805,6 @@ PyInit__core(void)
21084
20805
  return NULL;
21085
20806
  if (PyType_Ready(&StructMixinType) < 0)
21086
20807
  return NULL;
21087
- if (PyType_Ready(&Raw_Type) < 0)
21088
- return NULL;
21089
20808
  if (PyType_Ready(&JSONEncoder_Type) < 0)
21090
20809
  return NULL;
21091
20810
  if (PyType_Ready(&JSONDecoder_Type) < 0)
@@ -21116,8 +20835,6 @@ PyInit__core(void)
21116
20835
  if (PyModule_AddObjectRef(m, "TimezoneConstraint", (PyObject *)&TimezoneConstraint_Type) < 0)
21117
20836
  return NULL;
21118
20837
 
21119
- if (PyModule_AddObjectRef(m, "Raw", (PyObject *)&Raw_Type) < 0)
21120
- return NULL;
21121
20838
  if (PyModule_AddObjectRef(m, "JSONEncoder", (PyObject *)&JSONEncoder_Type) < 0)
21122
20839
  return NULL;
21123
20840
  if (PyModule_AddObjectRef(m, "JSONDecoder", (PyObject *)&JSONDecoder_Type) < 0)