Skip to content

Commit

Permalink
chore: clean up validation
Browse files Browse the repository at this point in the history
  • Loading branch information
machow committed Oct 13, 2023
1 parent d4eab0c commit 8c95044
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 9 deletions.
12 changes: 4 additions & 8 deletions quartodoc/autosummary.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@
from plum import dispatch # noqa
from pathlib import Path
from types import ModuleType
from pydantic import ValidationError

from .inventory import create_inventory, convert_inventory
from . import layout
from .parsers import get_parser_defaults
from .renderers import Renderer
from .validation import fmt
from .validation import fmt_all
from ._pydantic_compat import ValidationError


from typing import Any

Expand Down Expand Up @@ -485,12 +486,7 @@ def load_layout(self, sections: dict, package: str, options=None):
try:
return layout.Layout(sections=sections, package=package, options=options)
except ValidationError as e:
msg = "Configuration error for YAML:\n - "
errors = [fmt(err) for err in e.errors() if fmt(err)]
first_error = errors[
0
] # we only want to show one error at a time b/c it is confusing otherwise
msg += first_error
msg = fmt_all(e)
raise ValueError(msg) from None

# building ----------------------------------------------------------------
Expand Down
5 changes: 4 additions & 1 deletion quartodoc/tests/__snapshots__/test_validation.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

'''
# ---
# name: test_missing_name_contents_2
# name: test_missing_name_contents
'''
Code:

Expand All @@ -25,7 +25,10 @@
- title: Section 1
- title: Section 2
contents:

# name missing here ----
- children: linked

- name: MdRenderer

Error:
Expand Down
62 changes: 62 additions & 0 deletions quartodoc/validation.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,67 @@
"""User-friendly messages for configuration validation errors.
This module has largely two goals:
* Show the most useful error (pydantic starts with the highest, broadest one).
* Make the error message very user-friendly.
The key dynamic for understanding formatting is that pydantic is very forgiving.
It will coerce values to the target type, and by default allows extra fields.
Critically, if you have a union of types, it will try each type in order until it
finds a match. In this case, it reports errors for everything it tried.
For example, consider this config:
quartodoc:
package: zzz
sections:
- title: Section 1
contents:
# name missing here ----
- children: linked
In this case, the first element of contents is missing a name field. Since the
first type in the union for content elements is _AutoDefault, that is what it
tries (and logs an error about name). However, it then goes down the list of other
types in the union and logs errors for those (e.g. Doc). This produce a lot of
confusing messages, because nowhere does it make clear what type its trying to create.
We don't want error messages for everything it tried, just the first type in the union.
(For a discriminated union, it uses the `kind:` field to know what the first type to try is).
"""


def fmt_all(e):
# if not pydantic.__version__.startswith("1."):
# # error reports are much better in pydantic v2
# # so we just use those.
# return str(e)

errors = [fmt(err) for err in e.errors() if fmt(err)]

# the last error is the most specific, while earlier ones can be
# for alternative union types that didn't work out.
main_error = errors[0]

msg = f"Configuration error for YAML:\n - {main_error}"
return msg


def fmt(err: dict):
"format error messages from pydantic."

# each entry of loc is a new level in the config tree
# 0 is root
# 1 is sections
# 2 is a section entry
# 3 is contents
# 4 is a content item
# 5 might be Auto.members, etc..
# 6 might be an Auto.members item

# type: value_error.discriminated_union.missing_discriminator
# type: value_error.missing
# type: value_error.extra
msg = ""
if err["msg"].startswith("Discriminator"):
return msg
Expand Down

0 comments on commit 8c95044

Please sign in to comment.