From b8578e243055b6c29c5f3996a39f2389919fed83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tin=20Tvrtkovi=C4=87?= Date: Fri, 12 Jan 2024 00:45:59 +0100 Subject: [PATCH] More msgspec work --- docs/preconf.md | 17 +++++++++++++++++ src/cattrs/preconf/msgspec.py | 7 +++++++ tests/preconf/test_msgspec_cpython.py | 6 ++++++ 3 files changed, 30 insertions(+) diff --git a/docs/preconf.md b/docs/preconf.md index 4abe7352..a6ce06a3 100644 --- a/docs/preconf.md +++ b/docs/preconf.md @@ -92,6 +92,23 @@ Compatibility notes: - _attrs_ classes, dataclasses and sequences are handled directly by _msgspec_ if possible, otherwise by the normal _cattrs_ machinery. This means it's possible the validation errors produced may be _msgspec_ validation errors instead of _cattrs_ validation errors. +This converter supports {meth}`get_loads_hook() ` and {meth}`get_dumps_hook() `. +These are factories for dumping and loading functions (as opposed to unstructuring and structuring); the hooks returned by this may be further optimized to offload as much work as possible to _msgspec_. + +```python +>>> from cattrs.preconf.msgspec import make_converter + +>>> @define +... class Test: +... a: int + +>>> converter = make_converter() +>>> dumps = converter.get_dumps_hook(A) + +>>> dumps(Test(1)) # Will use msgspec directly. +b'{"a":1}' +``` + _msgspec_ doesn't support PyPy. ```{versionadded} 24.1.0 diff --git a/src/cattrs/preconf/msgspec.py b/src/cattrs/preconf/msgspec.py index 364b73be..1dd3edd1 100644 --- a/src/cattrs/preconf/msgspec.py +++ b/src/cattrs/preconf/msgspec.py @@ -3,6 +3,7 @@ from base64 import b64decode from datetime import date, datetime +from functools import partial from typing import Any, Callable, TypeVar, Union from attrs import has as attrs_has @@ -24,6 +25,8 @@ class MsgspecJsonConverter(Converter): + """A converter specialized for the _msgspec_ library.""" + #: The msgspec encoder for dumping. encoder: Encoder = Encoder() @@ -46,6 +49,10 @@ def loads(self, data: bytes, cl: type[T], **kwargs: Any) -> T: """Decode and structure `cl` from the provided JSON bytes.""" return self.structure(decode(data, **kwargs), cl) + def get_loads_hook(self, cl: type[T]) -> Callable[[bytes], T]: + """Produce a `loads` hook for the given type.""" + return partial(self.loads, cl=cl) + def configure_converter(converter: Converter) -> None: """Configure the converter for the msgspec library. diff --git a/tests/preconf/test_msgspec_cpython.py b/tests/preconf/test_msgspec_cpython.py index aaa59cc6..19a9add4 100644 --- a/tests/preconf/test_msgspec_cpython.py +++ b/tests/preconf/test_msgspec_cpython.py @@ -53,6 +53,12 @@ def test_dump_hook_attrs(converter: Conv): assert converter.get_dumps_hook(A) == converter.encoder.encode +def test_get_loads_hook(converter: Conv): + """`Converter.get_loads_hook` works.""" + hook = converter.get_loads_hook(A) + assert hook(b'{"a": 1}') == A(1) + + def test_basic_structs(converter: Conv): """Handling msgspec structs works."""