-
Notifications
You must be signed in to change notification settings - Fork 0
feat: ✨ add RequiredCheck
#122
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 8 commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
20ced70
feat: :sparkles: add RequiredRule
martonvago 520f04a
refactor: :recycle: simplify apply logic
martonvago 3b8a7c3
fix: :bug: remove unused import
martonvago e0f3ac9
fix: :bug: disallow all ambiguous JSON paths
martonvago 7a1b03d
Merge branch 'main' into feat/required-rule
martonvago 4ecfd81
Merge branch 'main' into feat/required-rule
martonvago 4a729b5
Merge branch 'main' into feat/required-rule
martonvago 11d80d4
Merge branch 'main' into feat/required-rule
martonvago 65ce0db
Merge branch 'main' into feat/required-rule
martonvago 3cb33cf
refactor: :recycle: make RequiredCheck not inherit from CustomCheck
martonvago 160d764
chore(pre-commit): :pencil2: automatic fixes
pre-commit-ci[bot] 7270e9b
refactor: :recycle: check JSON path in post_init
martonvago 639b9d9
Merge branch 'feat/required-rule' of github.com:seedcase-project/chec…
martonvago f7e949c
chore(pre-commit): :pencil2: automatic fixes
pre-commit-ci[bot] 37412b3
feat: :sparkles: disallow type required CustomChecks
martonvago a2c60d0
refactor: :recycle: rename to properties
martonvago b93c5f4
refactor: :recycle: remove protocol
martonvago 0195941
Merge branch 'main' into feat/required-rule
martonvago 016b046
refactor: :fire: remove init checks
martonvago 70af3f4
refactor: :recycle: review markups
martonvago File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,16 +1,18 @@ | ||
| import re | ||
| from dataclasses import dataclass | ||
| from typing import Any, Callable | ||
|
|
||
| from check_datapackage.internals import ( | ||
| _filter, | ||
| _flat_map, | ||
| _get_direct_jsonpaths, | ||
| _get_fields_at_jsonpath, | ||
| _map, | ||
| ) | ||
| from check_datapackage.issue import Issue | ||
|
|
||
|
|
||
| @dataclass | ||
| @dataclass(frozen=True) | ||
| class CustomCheck: | ||
| """A custom check to be done on a Data Package descriptor. | ||
|
|
||
|
|
@@ -44,6 +46,93 @@ class CustomCheck: | |
| check: Callable[[Any], bool] | ||
| type: str = "custom" | ||
|
|
||
| def apply(self, descriptor: dict[str, Any]) -> list[Issue]: | ||
martonvago marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
martonvago marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| """Checks the descriptor against this check and creates issues on failure. | ||
|
|
||
| Args: | ||
| descriptor: The descriptor to check. | ||
|
|
||
| Returns: | ||
| A list of `Issue`s. | ||
| """ | ||
| matching_fields = _get_fields_at_jsonpath(self.jsonpath, descriptor) | ||
| failed_fields = _filter( | ||
| matching_fields, lambda field: not self.check(field.value) | ||
| ) | ||
| return _map( | ||
| failed_fields, | ||
| lambda field: Issue( | ||
| jsonpath=field.jsonpath, type=self.type, message=self.message | ||
| ), | ||
| ) | ||
|
|
||
|
|
||
| class RequiredCheck(CustomCheck): | ||
martonvago marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| """A custom check that checks that a field is present (i.e. not None). | ||
|
|
||
| Attributes: | ||
| jsonpath (str): The location of the field or fields, expressed in [JSON | ||
| path](https://jg-rp.github.io/python-jsonpath/syntax/) notation, to which | ||
| the check applies (e.g., `$.resources[*].name`). | ||
| message (str): The message that is shown when the check fails. | ||
|
|
||
| Examples: | ||
| ```{python} | ||
| import check_datapackage as cdp | ||
| required_title_check = cdp.RequiredCheck( | ||
| jsonpath="$.title", | ||
| message="A title is required.", | ||
| ) | ||
| ``` | ||
| """ | ||
|
|
||
| _field_name: str | ||
martonvago marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| def __init__(self, jsonpath: str, message: str): | ||
| """Initializes the `RequiredCheck`.""" | ||
| field_name_match = re.search(r"(?<!\.)(\.\w+)$", jsonpath) | ||
|
||
| if not field_name_match: | ||
| raise ValueError( | ||
| f"Cannot define `RequiredCheck` for JSON path `{jsonpath}`." | ||
| " A `RequiredCheck` must target a concrete object field (e.g.," | ||
| " `$.title`) or set of fields (e.g., `$.resources[*].title`)." | ||
| " Ambiguous paths (e.g., `$..title`) or paths pointing to array items" | ||
martonvago marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| " (e.g., `$.resources[0]`) are not allowed." | ||
| ) | ||
|
|
||
| self._field_name = field_name_match.group(1) | ||
| super().__init__( | ||
| jsonpath=jsonpath, | ||
| message=message, | ||
| check=lambda value: value is not None, | ||
| type="required", | ||
| ) | ||
|
|
||
| def apply(self, descriptor: dict[str, Any]) -> list[Issue]: | ||
martonvago marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| """Checks the descriptor against this check and creates issues on failure. | ||
|
|
||
| Args: | ||
| descriptor: The descriptor to check. | ||
|
|
||
| Returns: | ||
| A list of `Issue`s. | ||
| """ | ||
| matching_paths = _get_direct_jsonpaths(self.jsonpath, descriptor) | ||
| indirect_parent_path = self.jsonpath.removesuffix(self._field_name) | ||
| direct_parent_paths = _get_direct_jsonpaths(indirect_parent_path, descriptor) | ||
| missing_paths = _filter( | ||
| direct_parent_paths, | ||
| lambda path: f"{path}{self._field_name}" not in matching_paths, | ||
| ) | ||
lwjohnst86 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return _map( | ||
| missing_paths, | ||
| lambda path: Issue( | ||
| jsonpath=path + self._field_name, | ||
| type=self.type, | ||
| message=self.message, | ||
| ), | ||
| ) | ||
|
|
||
|
|
||
| def apply_custom_checks( | ||
| custom_checks: list[CustomCheck], descriptor: dict[str, Any] | ||
|
|
@@ -59,34 +148,5 @@ def apply_custom_checks( | |
| """ | ||
| return _flat_map( | ||
| custom_checks, | ||
| lambda custom_check: _apply_custom_check(custom_check, descriptor), | ||
| ) | ||
|
|
||
|
|
||
| def _apply_custom_check( | ||
| custom_check: CustomCheck, descriptor: dict[str, Any] | ||
| ) -> list[Issue]: | ||
| """Applies the custom check to the descriptor. | ||
|
|
||
| If any fields fail the custom check, this function creates a list of issues | ||
| for those fields. | ||
|
|
||
| Args: | ||
| custom_check: The custom check to apply to the descriptor. | ||
| descriptor: The descriptor to check. | ||
|
|
||
| Returns: | ||
| A list of `Issue`s. | ||
| """ | ||
| matching_fields = _get_fields_at_jsonpath(custom_check.jsonpath, descriptor) | ||
| failed_fields = _filter( | ||
| matching_fields, lambda field: not custom_check.check(field.value) | ||
| ) | ||
| return _map( | ||
| failed_fields, | ||
| lambda field: Issue( | ||
| jsonpath=field.jsonpath, | ||
| type=custom_check.type, | ||
| message=custom_check.message, | ||
| ), | ||
| lambda check: check.apply(descriptor), | ||
| ) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.