From 78c99d6c1bf37a6589b901a214ac30b7ecdae8f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Furga=C5=82a?= <83299832+00200200@users.noreply.github.com> Date: Tue, 29 Sep 2026 13:24:49 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20FIX:=20Don't=20crash=20on=20nest?= =?UTF-8?q?ed=20YAML=20dates=20in=20front=20matter=20(#1203)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit json.dumps of front-matter mappings/lists raised TypeError for nested date/datetime values. Stringify those with default=str, and catch TypeError so a YAML date used as a mapping key warns instead of aborting the build. --- CHANGELOG.md | 6 ++++ myst_parser/mdit_to_docutils/base.py | 7 +++-- tests/test_docutils.py | 42 ++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff2d904f..c8c4d93e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### 🐛 Bug Fixes + +- 🐛 Don't crash when a YAML date/datetime is nested in front matter by + ## 5.1.0 - 2026-05-13 ### ✨ New Features diff --git a/myst_parser/mdit_to_docutils/base.py b/myst_parser/mdit_to_docutils/base.py index 68cfae72..fe54b5aa 100644 --- a/myst_parser/mdit_to_docutils/base.py +++ b/myst_parser/mdit_to_docutils/base.py @@ -1387,8 +1387,11 @@ def dict_to_fm_field_list( ) continue try: - value = json.dumps(value) - except (ValueError, RecursionError): + # Nested date/datetime values (allowed at top level) are + # not JSON-serializable; stringify them like scalars. + # TypeError also covers non-string keys such as a YAML date. + value = json.dumps(value, default=str) + except (ValueError, TypeError, RecursionError): # e.g. a self-referential structure via a YAML alias self.create_warning( f"Front matter field {key!r} could not be serialized", diff --git a/tests/test_docutils.py b/tests/test_docutils.py index 890badbc..43cb7baa 100644 --- a/tests/test_docutils.py +++ b/tests/test_docutils.py @@ -334,6 +334,48 @@ def test_topmatter_deeply_nested_yaml_warns(): assert "content" in doctree.pformat() +@pytest.mark.parametrize( + "yaml_block", + [ + "review:\n date: 2026-08-03", # date nested in a mapping + "reviewed: [2026-08-03]", # date nested in a sequence + "review:\n at: 2026-08-03T12:00:00", # datetime nested in a mapping + ], +) +def test_topmatter_nested_dates_do_not_crash(yaml_block): + """YAML dates nested in dicts/lists must not abort the build. + + Top-level ``date``/``datetime`` scalars skip JSON encoding, but a nested + date used to reach ``json.dumps`` and raise ``TypeError`` (#1203). + """ + stream = io.StringIO() + doctree = publish_doctree( + source=f"---\n{yaml_block}\n---\n\ncontent\n", + parser=Parser(), + settings_overrides={"warning_stream": stream}, + ) + assert "content" in doctree.pformat() + assert "2026-08-03" in doctree.astext() + assert "[myst.topmatter]" not in stream.getvalue() + + +def test_topmatter_date_mapping_key_warns(): + """A YAML date used as a mapping *key* cannot be JSON-serialized. + + ``json.dumps(..., default=str)`` does not convert keys, so this still + cannot be rendered as a field, but it must warn rather than crash. + """ + stream = io.StringIO() + doctree = publish_doctree( + source="---\nreview:\n 2026-08-03: done\n---\n\ncontent\n", + parser=Parser(), + settings_overrides={"warning_stream": stream}, + ) + assert "content" in doctree.pformat() + assert "could not be serialized" in stream.getvalue() + assert "[myst.topmatter]" in stream.getvalue() + + def test_topmatter_alias_expansion_bomb_warns(): """A YAML alias-expansion ("billion laughs") bomb warns, not hangs.