From 9514bf2f07946a6dd5e44e7088a52f456090d2b1 Mon Sep 17 00:00:00 2001 From: Joel Ostblom Date: Fri, 21 Nov 2025 17:12:38 +0100 Subject: [PATCH 01/20] feat: Create explain function to improve error formatting Hopefully makes errors easier to read --- src/check_datapackage/check.py | 22 +++++++++++++++++++++- src/check_datapackage/issue.py | 2 ++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/check_datapackage/check.py b/src/check_datapackage/check.py index 2d3a8c6c..cadb1dfa 100644 --- a/src/check_datapackage/check.py +++ b/src/check_datapackage/check.py @@ -52,7 +52,7 @@ def __init__( # TODO: Switch to using `explain()` once implemented errors: list[str] = _map( issues, - lambda issue: f"- Property `{issue.jsonpath}`: {issue.message}\n", + explain, ) message: str = ( "There were some issues found in your `datapackage.json`:\n\n" @@ -61,6 +61,25 @@ def __init__( super().__init__(message) +def explain(issue: Issue) -> str: + """Explain the issue in a human-readable format. + + Args: + issue: The `Issue` object to explain. + + Returns: + A human-readable explanation of the issue. + """ + property_name = issue.jsonpath.split(".")[-1] + return ( # noqa: F401 + f"At package.{issue.jsonpath[2:]}:\n" # indexing to remove '$.' + "|\n" + f"| {property_name}: {issue.instance!r}\n" + f"| {' ' * len(property_name)} {'^' * len(str(issue.instance))}\n" + f"{issue.message}\n" + ) + + def check( properties: dict[str, Any], config: Config = Config(), error: bool = False ) -> list[Issue]: @@ -578,6 +597,7 @@ def _create_issue(error: SchemaError) -> Issue: message=error.message, jsonpath=error.jsonpath, type=error.type, + instance=error.instance, ) diff --git a/src/check_datapackage/issue.py b/src/check_datapackage/issue.py index 13d23c83..d16857dc 100644 --- a/src/check_datapackage/issue.py +++ b/src/check_datapackage/issue.py @@ -1,4 +1,5 @@ from dataclasses import dataclass +from typing import Any @dataclass(order=True, frozen=True) @@ -31,3 +32,4 @@ class Issue: jsonpath: str type: str message: str + instance: Any From d014d998d6705f4ca58b2487c0649bacfeb87c2d Mon Sep 17 00:00:00 2001 From: Joel Ostblom Date: Fri, 21 Nov 2025 17:13:15 +0100 Subject: [PATCH 02/20] feat: Print how many issues were found --- src/check_datapackage/check.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/check_datapackage/check.py b/src/check_datapackage/check.py index cadb1dfa..aba24902 100644 --- a/src/check_datapackage/check.py +++ b/src/check_datapackage/check.py @@ -55,7 +55,7 @@ def __init__( explain, ) message: str = ( - "There were some issues found in your `datapackage.json`:\n\n" + f"There were {len(errors)} issues found in your `datapackage.json`:\n\n" + "\n".join(errors) ) super().__init__(message) From e763580b5689affefef518e7276d3c0c598d459a Mon Sep 17 00:00:00 2001 From: Joel Ostblom Date: Mon, 24 Nov 2025 09:15:50 +0100 Subject: [PATCH 03/20] refactor: Move message creation into separate function Moves all logic out of init() and instead into two separate functions: one for iterating over the errors and adding a general message, and one for formatting the message for each issue. --- src/check_datapackage/check.py | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/check_datapackage/check.py b/src/check_datapackage/check.py index afeba122..29fb2604 100644 --- a/src/check_datapackage/check.py +++ b/src/check_datapackage/check.py @@ -48,28 +48,32 @@ 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, - explain, - ) - message: str = ( - f"There were {len(errors)} issues found in your `datapackage.json`:\n\n" - + "\n".join(errors) - ) - super().__init__(message) + """Create the DataPackageError from issues.""" + super().__init__(explain(issues)) -def explain(issue: Issue) -> str: +def explain(issues: list[Issue]) -> str: """Explain the issue in a human-readable format. Args: - issue: The `Issue` object to explain. + issues: A list of `Issue` objects to explain. Returns: A human-readable explanation of the issue. """ + issue_messages: list[str] = _map( + issues, + _create_issue_message, + ) + error_message: str = ( + f"There were {len(issue_messages)} issues found in your `datapackage.json`:\n\n" + + "\n".join(issue_messages) + ) + return error_message + + +def _create_issue_message(issue: Issue) -> str: + """Create an informative message of what went wrong in each issue.""" property_name = issue.jsonpath.split(".")[-1] return ( # noqa: F401 f"At package.{issue.jsonpath[2:]}:\n" # indexing to remove '$.' From 8a16205847771182d64de556d07fe30eafc3045b Mon Sep 17 00:00:00 2001 From: Joel Ostblom Date: Mon, 24 Nov 2025 10:53:44 +0100 Subject: [PATCH 04/20] refactor: Rename for clarity --- src/check_datapackage/check.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/check_datapackage/check.py b/src/check_datapackage/check.py index 29fb2604..1fc821b5 100644 --- a/src/check_datapackage/check.py +++ b/src/check_datapackage/check.py @@ -61,19 +61,19 @@ def explain(issues: list[Issue]) -> str: Returns: A human-readable explanation of the issue. """ - issue_messages: list[str] = _map( + issue_explanations: list[str] = _map( issues, - _create_issue_message, + _create_explanation, ) error_message: str = ( - f"There were {len(issue_messages)} issues found in your `datapackage.json`:\n\n" - + "\n".join(issue_messages) + f"There were {len(issue_explanations)} issues found in your `datapackage.json`:\n\n" + + "\n".join(issue_explanations) ) return error_message -def _create_issue_message(issue: Issue) -> str: - """Create an informative message of what went wrong in each issue.""" +def _create_explanation(issue: Issue) -> str: + """Create an informative explanation of what went wrong in each issue.""" property_name = issue.jsonpath.split(".")[-1] return ( # noqa: F401 f"At package.{issue.jsonpath[2:]}:\n" # indexing to remove '$.' From 429e3abd1a22d8a32cd884f950edad5c7c764950 Mon Sep 17 00:00:00 2001 From: Joel Ostblom Date: Mon, 24 Nov 2025 11:17:31 +0100 Subject: [PATCH 05/20] fix: Shorten line --- src/check_datapackage/check.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/check_datapackage/check.py b/src/check_datapackage/check.py index 1fc821b5..66da4828 100644 --- a/src/check_datapackage/check.py +++ b/src/check_datapackage/check.py @@ -66,7 +66,7 @@ def explain(issues: list[Issue]) -> str: _create_explanation, ) error_message: str = ( - f"There were {len(issue_explanations)} issues found in your `datapackage.json`:\n\n" + f"{len(issue_explanations)} issues were found in your `datapackage.json`:\n\n" + "\n".join(issue_explanations) ) return error_message From 7cddc2afab8aa5555497d594cd43c14d147518cf Mon Sep 17 00:00:00 2001 From: Joel Ostblom Date: Mon, 24 Nov 2025 11:40:18 +0100 Subject: [PATCH 06/20] feat: Set default value for issue To avoid having to specify it everywhere where it is not used. --- src/check_datapackage/extensions.py | 2 ++ src/check_datapackage/issue.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/check_datapackage/extensions.py b/src/check_datapackage/extensions.py index 36185ca2..a0f47ec6 100644 --- a/src/check_datapackage/extensions.py +++ b/src/check_datapackage/extensions.py @@ -59,6 +59,7 @@ class CustomCheck(BaseModel, frozen=True): message: str check: Callable[[Any], bool] type: str = "custom" + instance: Any = None @field_validator("type", mode="after") @classmethod @@ -162,6 +163,7 @@ class RequiredCheck(BaseModel, frozen=True): jsonpath: JsonPath message: str _targets: list[TargetJsonPath] = PrivateAttr() + instance: Any = None @model_validator(mode="after") def _check_field_name_in_jsonpath(self) -> Self: diff --git a/src/check_datapackage/issue.py b/src/check_datapackage/issue.py index d16857dc..0fa2c4d2 100644 --- a/src/check_datapackage/issue.py +++ b/src/check_datapackage/issue.py @@ -32,4 +32,4 @@ class Issue: jsonpath: str type: str message: str - instance: Any + instance: Any = None From 422420b1bfb9c42f6bd9f2b4af4ce92b3ba5bca0 Mon Sep 17 00:00:00 2001 From: Joel Ostblom Date: Mon, 24 Nov 2025 18:14:32 +0100 Subject: [PATCH 07/20] fix: Only define `instance` when it is a hashable type For certain error types, a dict or list is set as the instance, which means logic elsewhere that calls `set()` on `Issue` fails. --- src/check_datapackage/check.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/check_datapackage/check.py b/src/check_datapackage/check.py index 66da4828..c9b9e683 100644 --- a/src/check_datapackage/check.py +++ b/src/check_datapackage/check.py @@ -602,7 +602,11 @@ def _create_issue(error: SchemaError) -> Issue: message=error.message, jsonpath=error.jsonpath, type=error.type, - instance=error.instance, + instance=( + None + if error.type in ("required", "minItems", "oneOf", "enum", "uniqueItems") + else error.instance, + ), ) From d9818b87a5226f186d0788cbe4ab757957382c5b Mon Sep 17 00:00:00 2001 From: Joel Ostblom Date: Tue, 25 Nov 2025 09:16:51 +0100 Subject: [PATCH 08/20] fix: Only add the error instance if it is hashable Since we perform deduplication of Issues later, we need to either only allow hashable items or write custome comparison logic to compare unhashable ones. Technically, writing custom comparison logic that ignores unhashable values might be the most theoretically appropriate (since it is redundant to compare `instance` when also comparing `jsonpath`), but it could look convoluted and have worse performance. This is a simpler solution that is easy to understand and that does not involve any significant performance penalty as the extra comparison is of small items. Trying this out as a first approach and changing if we notice that there are occasions where we would value having access to an unhashable instance. For more discussion, see: https://github.com/seedcase-project/check-datapackage/pull/208#discussion_r2555916842 --- src/check_datapackage/check.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/check_datapackage/check.py b/src/check_datapackage/check.py index c9b9e683..2ad6d916 100644 --- a/src/check_datapackage/check.py +++ b/src/check_datapackage/check.py @@ -3,7 +3,7 @@ from dataclasses import dataclass, field from functools import reduce from types import TracebackType -from typing import Any, Callable, Iterator, Optional +from typing import Any, Callable, Hashable, Iterator, Optional from jsonschema import Draft7Validator, FormatChecker, ValidationError @@ -602,11 +602,8 @@ def _create_issue(error: SchemaError) -> Issue: message=error.message, jsonpath=error.jsonpath, type=error.type, - instance=( - None - if error.type in ("required", "minItems", "oneOf", "enum", "uniqueItems") - else error.instance, - ), + # Since we want to deduplicate issues, only include instance if it's hashable + instance=error.instance if isinstance(error.instance, Hashable) else None, ) From 52be8e2e336a6e5bfd0435a29d6edf976a3325b1 Mon Sep 17 00:00:00 2001 From: Joel Ostblom Date: Tue, 25 Nov 2025 10:01:57 +0100 Subject: [PATCH 09/20] feat: Expose explain at top level --- src/check_datapackage/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/check_datapackage/__init__.py b/src/check_datapackage/__init__.py index d4daf3c3..a73d5fc7 100644 --- a/src/check_datapackage/__init__.py +++ b/src/check_datapackage/__init__.py @@ -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, @@ -24,5 +24,6 @@ "example_resource_properties", "example_field_properties", "check", + "explain", "read_json", ] From cf6bd9014d09d2ff03c5f4f5de38b9c645f2ede4 Mon Sep 17 00:00:00 2001 From: Joel Ostblom Date: Tue, 25 Nov 2025 10:03:45 +0100 Subject: [PATCH 10/20] docs: Add explain example --- src/check_datapackage/check.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/check_datapackage/check.py b/src/check_datapackage/check.py index 2ad6d916..dd4627c4 100644 --- a/src/check_datapackage/check.py +++ b/src/check_datapackage/check.py @@ -60,6 +60,19 @@ def explain(issues: list[Issue]) -> str: Returns: A human-readable explanation of the issue. + + 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.", + ) + + cdp.explain([issue]) + ``` """ issue_explanations: list[str] = _map( issues, From 05a6b017f1b75bfe9e3a6fb4e932ff55f1aac42d Mon Sep 17 00:00:00 2001 From: Joel Ostblom Date: Tue, 25 Nov 2025 10:06:45 +0100 Subject: [PATCH 11/20] docs: Show sections on `explain` --- docs/design/interface.qmd | 2 +- docs/guide/issues.qmd | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/docs/design/interface.qmd b/docs/design/interface.qmd index e829d050..ca9ad359 100644 --- a/docs/design/interface.qmd +++ b/docs/design/interface.qmd @@ -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 diff --git a/docs/guide/issues.qmd b/docs/guide/issues.qmd index 2362e744..ebdd97bb 100644 --- a/docs/guide/issues.qmd +++ b/docs/guide/issues.qmd @@ -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. - - -::: content-hidden ## The `explain()` function The `explain()` function provides a more verbose and user-friendly @@ -40,4 +37,3 @@ cdp.explain(issues) When setting `error=True` in `check()`, error messages will be generated by the `explain()` function. -::: From 3d904e761c5a5b8d5386b57b1fcf34a5476c091f Mon Sep 17 00:00:00 2001 From: Joel Ostblom Date: Tue, 25 Nov 2025 11:37:15 +0100 Subject: [PATCH 12/20] fix: Remove unnecessary `instance` initializations --- src/check_datapackage/extensions.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/check_datapackage/extensions.py b/src/check_datapackage/extensions.py index a0f47ec6..36185ca2 100644 --- a/src/check_datapackage/extensions.py +++ b/src/check_datapackage/extensions.py @@ -59,7 +59,6 @@ class CustomCheck(BaseModel, frozen=True): message: str check: Callable[[Any], bool] type: str = "custom" - instance: Any = None @field_validator("type", mode="after") @classmethod @@ -163,7 +162,6 @@ class RequiredCheck(BaseModel, frozen=True): jsonpath: JsonPath message: str _targets: list[TargetJsonPath] = PrivateAttr() - instance: Any = None @model_validator(mode="after") def _check_field_name_in_jsonpath(self) -> Self: From 22590e9823af25cd68ae30e316a0a6fcdbbfa4c6 Mon Sep 17 00:00:00 2001 From: Joel Ostblom Date: Tue, 25 Nov 2025 11:50:14 +0100 Subject: [PATCH 13/20] docs: Add docstring --- src/check_datapackage/issue.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/check_datapackage/issue.py b/src/check_datapackage/issue.py index 0fa2c4d2..940f281a 100644 --- a/src/check_datapackage/issue.py +++ b/src/check_datapackage/issue.py @@ -16,6 +16,7 @@ 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. Examples: ```{python} From 9359e7d184502d608d21106f9ef42f22882a0d47 Mon Sep 17 00:00:00 2001 From: Joel Ostblom Date: Tue, 25 Nov 2025 13:16:39 +0100 Subject: [PATCH 14/20] fix: Account for quotes when printing string instances --- src/check_datapackage/check.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/check_datapackage/check.py b/src/check_datapackage/check.py index dd4627c4..9c866a38 100644 --- a/src/check_datapackage/check.py +++ b/src/check_datapackage/check.py @@ -88,11 +88,13 @@ def explain(issues: list[Issue]) -> str: def _create_explanation(issue: Issue) -> str: """Create an informative explanation of what went wrong in each issue.""" property_name = issue.jsonpath.split(".")[-1] + # Two extra carets are added for string instances to account for quotes + number_of_carets = len(str(issue.instance)) + 2 * isinstance(issue.instance, str) return ( # noqa: F401 f"At package.{issue.jsonpath[2:]}:\n" # indexing to remove '$.' "|\n" f"| {property_name}: {issue.instance!r}\n" - f"| {' ' * len(property_name)} {'^' * len(str(issue.instance))}\n" + f"| {' ' * len(property_name)} {'^' * number_of_carets}\n" f"{issue.message}\n" ) From 3c4798b7071d3e039380e490b59913cd691a50e1 Mon Sep 17 00:00:00 2001 From: Joel Ostblom Date: Wed, 26 Nov 2025 11:30:32 +0100 Subject: [PATCH 15/20] feat: Exclude Issue instance from comparison and hash But always include it in the Issue object instead of sometimes excluding it based on whether it is hashable or not. See further discussion in https://github.com/seedcase-project/check-datapackage/pull/208#discussion_r2555916842 --- src/check_datapackage/check.py | 5 ++--- src/check_datapackage/issue.py | 7 ++++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/check_datapackage/check.py b/src/check_datapackage/check.py index 9c866a38..7610a8c9 100644 --- a/src/check_datapackage/check.py +++ b/src/check_datapackage/check.py @@ -3,7 +3,7 @@ from dataclasses import dataclass, field from functools import reduce from types import TracebackType -from typing import Any, Callable, Hashable, Iterator, Optional +from typing import Any, Callable, Iterator, Optional from jsonschema import Draft7Validator, FormatChecker, ValidationError @@ -617,8 +617,7 @@ def _create_issue(error: SchemaError) -> Issue: message=error.message, jsonpath=error.jsonpath, type=error.type, - # Since we want to deduplicate issues, only include instance if it's hashable - instance=error.instance if isinstance(error.instance, Hashable) else None, + instance=error.instance, ) diff --git a/src/check_datapackage/issue.py b/src/check_datapackage/issue.py index 940f281a..99240e1b 100644 --- a/src/check_datapackage/issue.py +++ b/src/check_datapackage/issue.py @@ -1,4 +1,4 @@ -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import Any @@ -16,7 +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. + instance (Any): The part of the object that failed the check. This field is not + considered when comparing or hashing `Issue` objects. Examples: ```{python} @@ -33,4 +34,4 @@ class Issue: jsonpath: str type: str message: str - instance: Any = None + instance: Any = field(default=None, compare=False, hash=False) From 70c8a697767d6cfaf1441ad3a86029654c97e0a8 Mon Sep 17 00:00:00 2001 From: Joel Ostblom Date: Thu, 27 Nov 2025 08:00:04 +0100 Subject: [PATCH 16/20] fix: Fix typos Co-authored-by: martonvago <57952344+martonvago@users.noreply.github.com> --- src/check_datapackage/check.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/check_datapackage/check.py b/src/check_datapackage/check.py index 7610a8c9..9915eda2 100644 --- a/src/check_datapackage/check.py +++ b/src/check_datapackage/check.py @@ -53,13 +53,13 @@ def __init__( def explain(issues: list[Issue]) -> str: - """Explain the issue in a human-readable format. + """Explain the issues in a human-readable format. Args: issues: A list of `Issue` objects to explain. Returns: - A human-readable explanation of the issue. + A human-readable explanation of the issues. Examples: ```{python} From e393307b45014c2db0815ee3fb839ec5e790d5ba Mon Sep 17 00:00:00 2001 From: Joel Ostblom Date: Thu, 27 Nov 2025 08:13:56 +0100 Subject: [PATCH 17/20] fix: Skip unnecessary variable assignment --- src/check_datapackage/check.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/check_datapackage/check.py b/src/check_datapackage/check.py index 9915eda2..a09bd63a 100644 --- a/src/check_datapackage/check.py +++ b/src/check_datapackage/check.py @@ -78,11 +78,10 @@ def explain(issues: list[Issue]) -> str: issues, _create_explanation, ) - error_message: str = ( + return ( f"{len(issue_explanations)} issues were found in your `datapackage.json`:\n\n" + "\n".join(issue_explanations) ) - return error_message def _create_explanation(issue: Issue) -> str: From 7f3cc28364df77558dc48e96b2a86440d3ab726d Mon Sep 17 00:00:00 2001 From: Joel Ostblom Date: Thu, 27 Nov 2025 08:26:22 +0100 Subject: [PATCH 18/20] fix: Correct grammatical number --- src/check_datapackage/check.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/check_datapackage/check.py b/src/check_datapackage/check.py index a09bd63a..3d81eb76 100644 --- a/src/check_datapackage/check.py +++ b/src/check_datapackage/check.py @@ -78,8 +78,10 @@ def explain(issues: list[Issue]) -> str: issues, _create_explanation, ) + num_issues = len(issue_explanations) + singular_or_plural = " was" if num_issues == 1 else "s were" return ( - f"{len(issue_explanations)} issues were found in your `datapackage.json`:\n\n" + f"{num_issues} issue{singular_or_plural} found in your `datapackage.json`:\n\n" + "\n".join(issue_explanations) ) From 3b036207d820c7e8d430e85a158dfaa3a04d8fdc Mon Sep 17 00:00:00 2001 From: Joel Ostblom Date: Thu, 27 Nov 2025 09:00:31 +0100 Subject: [PATCH 19/20] feat: Account for `[]` passed as the object --- src/check_datapackage/check.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/check_datapackage/check.py b/src/check_datapackage/check.py index 3d81eb76..66a9baeb 100644 --- a/src/check_datapackage/check.py +++ b/src/check_datapackage/check.py @@ -88,13 +88,14 @@ def explain(issues: list[Issue]) -> str: def _create_explanation(issue: Issue) -> str: """Create an informative explanation of what went wrong in each issue.""" - property_name = issue.jsonpath.split(".")[-1] + # Remove suffix '$' to account for root path when `[]` is passed to `check()` + property_name = issue.jsonpath.removesuffix("$").split(".")[-1] # Two extra carets are added for string instances to account for quotes number_of_carets = len(str(issue.instance)) + 2 * isinstance(issue.instance, str) return ( # noqa: F401 - f"At package.{issue.jsonpath[2:]}:\n" # indexing to remove '$.' + f"At package{issue.jsonpath.removeprefix('$')}:\n" "|\n" - f"| {property_name}: {issue.instance!r}\n" + f"| {property_name}{': ' if property_name else ' '}{issue.instance}\n" f"| {' ' * len(property_name)} {'^' * number_of_carets}\n" f"{issue.message}\n" ) From 8a8da09adf214bc2402f1e5b18c70d9dec874614 Mon Sep 17 00:00:00 2001 From: Joel Ostblom Date: Thu, 27 Nov 2025 09:06:39 +0100 Subject: [PATCH 20/20] fix: Don't include extra carets since we no longer have the quotes printed --- src/check_datapackage/check.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/check_datapackage/check.py b/src/check_datapackage/check.py index 66a9baeb..666159ac 100644 --- a/src/check_datapackage/check.py +++ b/src/check_datapackage/check.py @@ -90,8 +90,7 @@ 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] - # Two extra carets are added for string instances to account for quotes - number_of_carets = len(str(issue.instance)) + 2 * isinstance(issue.instance, str) + number_of_carets = len(str(issue.instance)) return ( # noqa: F401 f"At package{issue.jsonpath.removeprefix('$')}:\n" "|\n"