Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
9514bf2
feat: Create explain function to improve error formatting
joelostblom Nov 21, 2025
d014d99
feat: Print how many issues were found
joelostblom Nov 21, 2025
3d2d197
Merge branch 'main' into feat/explain-errors
lwjohnst86 Nov 21, 2025
e763580
refactor: Move message creation into separate function
joelostblom Nov 24, 2025
8a16205
refactor: Rename for clarity
joelostblom Nov 24, 2025
429e3ab
fix: Shorten line
joelostblom Nov 24, 2025
7cddc2a
feat: Set default value for issue
joelostblom Nov 24, 2025
422420b
fix: Only define `instance` when it is a hashable type
joelostblom Nov 24, 2025
d9818b8
fix: Only add the error instance if it is hashable
joelostblom Nov 25, 2025
d89620e
Merge branch 'main' into feat/explain-errors
lwjohnst86 Nov 25, 2025
52be8e2
feat: Expose explain at top level
joelostblom Nov 25, 2025
cf6bd90
docs: Add explain example
joelostblom Nov 25, 2025
05a6b01
docs: Show sections on `explain`
joelostblom Nov 25, 2025
3d904e7
fix: Remove unnecessary `instance` initializations
joelostblom Nov 25, 2025
22590e9
docs: Add docstring
joelostblom Nov 25, 2025
9359e7d
fix: Account for quotes when printing string instances
joelostblom Nov 25, 2025
8677e44
Merge branch 'main' into feat/explain-errors
lwjohnst86 Nov 25, 2025
3c4798b
feat: Exclude Issue instance from comparison and hash
joelostblom Nov 26, 2025
70c8a69
fix: Fix typos
joelostblom Nov 27, 2025
e393307
fix: Skip unnecessary variable assignment
joelostblom Nov 27, 2025
7f3cc28
fix: Correct grammatical number
joelostblom Nov 27, 2025
3b03620
feat: Account for `[]` passed as the object
joelostblom Nov 27, 2025
8a8da09
fix: Don't include extra carets since we no longer have the quotes pr…
joelostblom Nov 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/design/interface.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ decide when and how failed checks should be enforced or handled. The
output of this function is a list of `Issue` objects, which are
described below.

### {{< var planned >}} `explain()`
### {{< var done >}} `explain()`

The output of `check()` is a list of `Issue` objects, which are
structured and machine-readable, but not very human-readable and
Expand Down
4 changes: 0 additions & 4 deletions docs/guide/issues.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@ properties. An `Issue` has attributes that provide information about the
failed check, which are described in more detail in the
[`Issue`](/docs/reference/Issue.qmd) documentation.

<!-- TODO: Unhide this after it has been implemented. -->

::: content-hidden
## The `explain()` function

The `explain()` function provides a more verbose and user-friendly
Expand All @@ -40,4 +37,3 @@ cdp.explain(issues)

When setting `error=True` in `check()`, error messages will be generated
by the `explain()` function.
:::
3 changes: 2 additions & 1 deletion src/check_datapackage/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Check functions and constants for the Frictionless Data Package standard."""

from .check import DataPackageError, check
from .check import DataPackageError, check, explain
from .config import Config
from .examples import (
example_field_properties,
Expand All @@ -24,5 +24,6 @@
"example_resource_properties",
"example_field_properties",
"check",
"explain",
"read_json",
]
60 changes: 50 additions & 10 deletions src/check_datapackage/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,56 @@ def __init__(
self,
issues: list[Issue],
) -> None:
"""Create the DataPackageError attributes from issues."""
# TODO: Switch to using `explain()` once implemented
errors: list[str] = _map(
issues,
lambda issue: f"- Property `{issue.jsonpath}`: {issue.message}\n",
)
message: str = (
"There were some issues found in your `datapackage.json`:\n\n"
+ "\n".join(errors)
"""Create the DataPackageError from issues."""
super().__init__(explain(issues))


def explain(issues: list[Issue]) -> str:
"""Explain the issues in a human-readable format.

Args:
issues: A list of `Issue` objects to explain.

Returns:
A human-readable explanation of the issues.

Examples:
```{python}
import check_datapackage as cdp

issue = cdp.Issue(
jsonpath="$.resources[2].title",
type="required",
message="The `title` field is required but missing at the given JSON path.",
)
super().__init__(message)

cdp.explain([issue])
```
"""
issue_explanations: list[str] = _map(
issues,
_create_explanation,
)
num_issues = len(issue_explanations)
singular_or_plural = " was" if num_issues == 1 else "s were"
return (
f"{num_issues} issue{singular_or_plural} found in your `datapackage.json`:\n\n"
+ "\n".join(issue_explanations)
)


def _create_explanation(issue: Issue) -> str:
"""Create an informative explanation of what went wrong in each issue."""
# Remove suffix '$' to account for root path when `[]` is passed to `check()`
property_name = issue.jsonpath.removesuffix("$").split(".")[-1]
number_of_carets = len(str(issue.instance))
return ( # noqa: F401
f"At package{issue.jsonpath.removeprefix('$')}:\n"
"|\n"
f"| {property_name}{': ' if property_name else ' '}{issue.instance}\n"
f"| {' ' * len(property_name)} {'^' * number_of_carets}\n"
f"{issue.message}\n"
)


def check(
Expand Down Expand Up @@ -579,6 +618,7 @@ def _create_issue(error: SchemaError) -> Issue:
message=error.message,
jsonpath=error.jsonpath,
type=error.type,
instance=error.instance,
)


Expand Down
6 changes: 5 additions & 1 deletion src/check_datapackage/issue.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from dataclasses import dataclass
from dataclasses import dataclass, field
from typing import Any


@dataclass(order=True, frozen=True)
Expand All @@ -15,6 +16,8 @@ class Issue:
as "required", "type", "pattern", or "format", or a custom type). Used to
exclude specific types of issues.
message (string): A description of what exactly the issue is.
instance (Any): The part of the object that failed the check. This field is not
considered when comparing or hashing `Issue` objects.

Examples:
```{python}
Expand All @@ -31,3 +34,4 @@ class Issue:
jsonpath: str
type: str
message: str
instance: Any = field(default=None, compare=False, hash=False)