Skip to content

Commit 08bd8d9

Browse files
authored
tomlkit: handle native date objects (#708)
* tomlkit: handle native date and datetime objects * More history * Tweak only for dates * Remove test? * Improve test * Add test
1 parent 1a94abf commit 08bd8d9

File tree

4 files changed

+39
-6
lines changed

4 files changed

+39
-6
lines changed

HISTORY.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,14 @@ Our backwards-compatibility policy can be found [here](https://github.com/python
2020
- Apply the attrs converter to the default value before checking if it is equal to the attribute's value, when `omit_if_default` is true and an attrs converter is specified.
2121
([#696](https://github.com/python-attrs/cattrs/pull/696))
2222
- Use the optional `_value_` type hint to structure and unstructure enums if present.
23-
([##699](https://github.com/python-attrs/cattrs/issues/699))
23+
([#699](https://github.com/python-attrs/cattrs/issues/699))
2424
- _cattrs_ now tracks performance using [codspeed](https://codspeed.io/python-attrs/cattrs).
2525
([#703](https://github.com/python-attrs/cattrs/pull/703))
26+
- The {mod}`tomlkit <cattrs.preconf.tomlkit>` preconf converter now properly handles native `date` objects when structuring.
27+
([#707](https://github.com/python-attrs/cattrs/issues/707) [#708](https://github.com/python-attrs/cattrs/pull/708))
28+
- The {mod}`tomlkit <cattrs.preconf.tomlkit>` preconf converter now passes date objects directly to _tomlkit_ for unstructuring.
29+
([#707](https://github.com/python-attrs/cattrs/issues/707) [#708](https://github.com/python-attrs/cattrs/pull/708))
30+
2631

2732
## 25.3.0 (2025-10-07)
2833

docs/preconf.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ Optional install targets should match the name of the {mod}`cattrs.preconf` modu
5555
# Using pip
5656
$ pip install cattrs[ujson]
5757

58+
# Usinv uv
59+
$ uv add cattrs[msgspec]
60+
5861
# Using pdm
5962
$ pdm add cattrs[orjson]
6063

@@ -202,4 +205,6 @@ Found at {mod}`cattrs.preconf.tomlkit`.
202205

203206
Bytes are serialized as base 85 strings. Sets are serialized as lists, and deserialized back into sets.
204207
Tuples are serialized as lists, and deserialized back into tuples.
205-
_tomlkit_ only supports mappings with string keys so mappings will have their keys stringified before serialization, and destringified during deserialization. `date` s are serialized as ISO 8601 strings.
208+
_tomlkit_ only supports mappings with string keys so mappings will have their keys stringified before serialization, and destringified during deserialization.
209+
[`date`](https://docs.python.org/3/library/datetime.html#datetime.date) and [`datetime`](https://docs.python.org/3/library/datetime.html#datetime.datetime) objects are passed through to be unstructured by _tomlkit_ itself.
210+

src/cattrs/preconf/tomlkit.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
from .._compat import is_mapping, is_subclass
1414
from ..converters import BaseConverter, Converter
15+
from ..fns import identity
1516
from ..strategies import configure_union_passthrough
1617
from . import validate_datetime, wrap
1718

@@ -37,6 +38,9 @@ def configure_converter(converter: BaseConverter):
3738
* sets are serialized as lists
3839
* tuples are serializas as lists
3940
* mapping keys are coerced into strings when unstructuring
41+
42+
.. versionchanged:: NEXT
43+
date objects are now passed through to tomlkit without unstructuring.
4044
"""
4145
converter.register_structure_hook(bytes, lambda v, _: b85decode(v))
4246
converter.register_unstructure_hook(
@@ -67,10 +71,12 @@ def key_handler(k: bytes):
6771

6872
# datetime inherits from date, so identity unstructure hook used
6973
# here to prevent the date unstructure hook running.
70-
converter.register_unstructure_hook(datetime, lambda v: v)
74+
converter.register_unstructure_hook(datetime, identity)
7175
converter.register_structure_hook(datetime, validate_datetime)
72-
converter.register_unstructure_hook(date, lambda v: v.isoformat())
73-
converter.register_structure_hook(date, lambda v, _: date.fromisoformat(v))
76+
converter.register_unstructure_hook(date, identity)
77+
converter.register_structure_hook(
78+
date, lambda v, _: v if isinstance(v, date) else date.fromisoformat(v)
79+
)
7480
configure_union_passthrough(
7581
Union[str, String, bool, int, Integer, float, Float], converter
7682
)

tests/test_preconf.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -719,7 +719,12 @@ def test_tomlkit(everything: Everything, detailed_validation: bool):
719719
converter = tomlkit_make_converter(detailed_validation=detailed_validation)
720720
unstructured = converter.unstructure(everything)
721721
raw = tomlkit_dumps(unstructured)
722-
assert converter.structure(tomlkit_loads(raw), Everything) == everything
722+
723+
loaded = tomlkit_loads(raw)
724+
# Check if we're unstructuring dates to native toml dates
725+
assert isinstance(loaded["a_date"], date)
726+
727+
assert converter.structure(loaded, Everything) == everything
723728

724729

725730
@given(
@@ -767,6 +772,18 @@ def test_tomlkit_unions(union_and_val: tuple, detailed_validation: bool):
767772
assert converter.structure(val, type) == val
768773

769774

775+
def test_tomlkit_date_strings():
776+
"""Dates represented as strings in toml work."""
777+
converter = tomlkit_make_converter()
778+
779+
@define
780+
class A:
781+
a_date: date
782+
783+
data = 'a_date = "2023-01-01"'
784+
assert converter.loads(data, A) == A(date(2023, 1, 1))
785+
786+
770787
@given(everythings(min_int=-9223372036854775808, max_int=18446744073709551615))
771788
def test_cbor2(everything: Everything):
772789
from cbor2 import dumps as cbor2_dumps

0 commit comments

Comments
 (0)