-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: ✨ add functions to check required and blank fields (#963)
## Description This PR adds functions for checking that (Sprout-specific) required fields are present and not blank. <!-- Select quick/in-depth as necessary --> This PR needs an in-depth review. ## Checklist - [x] Added or updated tests - [x] Updated documentation - [x] Ran `just run-all` --------- Co-authored-by: Luke W. Johnston <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Signe Kirk Brødbæk <[email protected]>
- Loading branch information
1 parent
0a73582
commit 0f4fa55
Showing
8 changed files
with
408 additions
and
0 deletions.
There are no files selected for viewing
39 changes: 39 additions & 0 deletions
39
seedcase_sprout/core/sprout_checks/check_fields_not_blank.py
This file contains 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 |
---|---|---|
@@ -0,0 +1,39 @@ | ||
from seedcase_sprout.core.checks.check_error import CheckError | ||
from seedcase_sprout.core.checks.required_fields import RequiredFieldType | ||
from seedcase_sprout.core.sprout_checks.get_blank_value_for_type import ( | ||
get_blank_value_for_type, | ||
) | ||
from seedcase_sprout.core.sprout_checks.get_json_path_to_resource_field import ( | ||
get_json_path_to_resource_field, | ||
) | ||
|
||
SPROUT_BLANK_ERROR_MESSAGE = "The '{field_name}' field is blank, please fill it in." | ||
|
||
|
||
def check_fields_not_blank( | ||
properties: dict, fields: dict[str, RequiredFieldType], index: int | None = None | ||
) -> list[CheckError]: | ||
"""Checks that fields in `fields` are not blank if they are present. | ||
Fields not present in `properties` are not checked. | ||
For resource properties, an index may be supplied, if the resource properties are | ||
part of a set of package properties. | ||
Args: | ||
properties: The properties where the fields are. | ||
fields: A set of fields and their types. | ||
index: The index of the resource properties. Defaults to None. | ||
Returns: | ||
A list of errors. An empty list if no errors were found. | ||
""" | ||
return [ | ||
CheckError( | ||
message=SPROUT_BLANK_ERROR_MESSAGE.format(field_name=field), | ||
json_path=get_json_path_to_resource_field(field, index), | ||
validator="blank", | ||
) | ||
for field, type in fields.items() | ||
if properties.get(field) == get_blank_value_for_type(type) | ||
] |
36 changes: 36 additions & 0 deletions
36
seedcase_sprout/core/sprout_checks/check_fields_present.py
This file contains 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 |
---|---|---|
@@ -0,0 +1,36 @@ | ||
from seedcase_sprout.core.checks.check_error import CheckError | ||
from seedcase_sprout.core.checks.required_fields import RequiredFieldType | ||
from seedcase_sprout.core.sprout_checks.get_json_path_to_resource_field import ( | ||
get_json_path_to_resource_field, | ||
) | ||
|
||
CHECKS_REQUIRED_ERROR_MESSAGE = "'{field_name}' is a required property" | ||
|
||
|
||
def check_fields_present( | ||
properties: dict, | ||
required_fields: dict[str, RequiredFieldType], | ||
index: int | None = None, | ||
) -> list[CheckError]: | ||
"""Checks that all fields in `required_fields` are present. | ||
For resource properties, an index may be supplied, if the resource properties are | ||
part of a set of package properties. | ||
Args: | ||
properties: The properties to check. | ||
required_fields: The set of required fields and their types. | ||
index: The index of the resource properties. Defaults to None. | ||
Returns: | ||
A list of errors. An empty list if no errors were found. | ||
""" | ||
return [ | ||
CheckError( | ||
message=CHECKS_REQUIRED_ERROR_MESSAGE.format(field_name=field), | ||
json_path=get_json_path_to_resource_field(field, index), | ||
validator="required", | ||
) | ||
for field in required_fields.keys() | ||
if properties.get(field) is None | ||
] |
33 changes: 33 additions & 0 deletions
33
seedcase_sprout/core/sprout_checks/check_list_item_field_not_blank.py
This file contains 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 |
---|---|---|
@@ -0,0 +1,33 @@ | ||
from seedcase_sprout.core.checks.check_error import CheckError | ||
from seedcase_sprout.core.checks.required_fields import RequiredFieldType | ||
from seedcase_sprout.core.sprout_checks.check_fields_not_blank import ( | ||
SPROUT_BLANK_ERROR_MESSAGE, | ||
) | ||
from seedcase_sprout.core.sprout_checks.get_blank_value_for_type import ( | ||
get_blank_value_for_type, | ||
) | ||
|
||
|
||
def check_list_item_field_not_blank( | ||
properties: dict, list_name: str, field_name: str, field_type=RequiredFieldType.str | ||
) -> list[CheckError]: | ||
"""Checks that the specified field of items in a list is not blank. | ||
Args: | ||
properties: The properties object containing the list. | ||
list_name: The name of the list field. | ||
field_name: The name of the item field. | ||
field_type: The type of the item field. Defaults to str. | ||
Returns: | ||
A list of errors. An empty list if no errors were found. | ||
""" | ||
return [ | ||
CheckError( | ||
message=SPROUT_BLANK_ERROR_MESSAGE.format(field_name=field_name), | ||
json_path=f"$.{list_name}[{index}].{field_name}", | ||
validator="blank", | ||
) | ||
for index, item in enumerate(properties.get(list_name, [])) | ||
if item.get(field_name) == get_blank_value_for_type(field_type) | ||
] |
32 changes: 32 additions & 0 deletions
32
seedcase_sprout/core/sprout_checks/check_required_package_properties_not_blank.py
This file contains 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 |
---|---|---|
@@ -0,0 +1,32 @@ | ||
from seedcase_sprout.core.checks.check_error import CheckError | ||
from seedcase_sprout.core.sprout_checks.check_fields_not_blank import ( | ||
check_fields_not_blank, | ||
) | ||
from seedcase_sprout.core.sprout_checks.check_list_item_field_not_blank import ( | ||
check_list_item_field_not_blank, | ||
) | ||
from seedcase_sprout.core.sprout_checks.required_fields import ( | ||
PACKAGE_SPROUT_REQUIRED_FIELDS, | ||
) | ||
|
||
|
||
def check_required_package_properties_not_blank( | ||
properties: dict, | ||
) -> list[CheckError]: | ||
"""Checks that required package properties fields are not blank. | ||
Both Sprout-specific required fields and fields required by the Data Package | ||
standard are checked. | ||
Args: | ||
properties: The package properties. | ||
Returns: | ||
A list of errors. An empty list if no errors were found. | ||
""" | ||
errors = check_fields_not_blank(properties, PACKAGE_SPROUT_REQUIRED_FIELDS) | ||
errors += check_list_item_field_not_blank(properties, "contributors", "title") | ||
errors += check_list_item_field_not_blank(properties, "sources", "title") | ||
errors += check_list_item_field_not_blank(properties, "licenses", "name") | ||
errors += check_list_item_field_not_blank(properties, "licenses", "path") | ||
return errors |
This file contains 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 |
---|---|---|
@@ -0,0 +1,57 @@ | ||
from pytest import mark | ||
|
||
from seedcase_sprout.core.checks.required_fields import RequiredFieldType | ||
from seedcase_sprout.core.sprout_checks.check_fields_not_blank import ( | ||
check_fields_not_blank, | ||
) | ||
from seedcase_sprout.core.sprout_checks.get_json_path_to_resource_field import ( | ||
get_json_path_to_resource_field, | ||
) | ||
|
||
FIELDS = {"name": RequiredFieldType.str, "tags": RequiredFieldType.list} | ||
|
||
|
||
@mark.parametrize("index", [None, 2]) | ||
def test_no_error_found_in_properties_with_populated_fields(index): | ||
"""Should pass properties with fields populated.""" | ||
properties = {"name": "My name", "tags": ["a", "b"]} | ||
|
||
assert check_fields_not_blank(properties, FIELDS, index) == [] | ||
|
||
|
||
@mark.parametrize("index", [None, 2]) | ||
def test_no_error_found_in_properties_with_fields_missing(index): | ||
"""Should pass properties without the specified fields.""" | ||
assert check_fields_not_blank({}, FIELDS, index) == [] | ||
|
||
|
||
@mark.parametrize("index", [None, 2]) | ||
def test_error_found_if_properties_have_a_blank_field(index): | ||
"""Should find an error if properties contain a blank field.""" | ||
properties = {"name": "My name", "tags": []} | ||
|
||
errors = check_fields_not_blank(properties, FIELDS, index) | ||
|
||
assert len(errors) == 1 | ||
assert "blank" in errors[0].message | ||
assert errors[0].json_path == get_json_path_to_resource_field("tags", index) | ||
assert errors[0].validator == "blank" | ||
|
||
|
||
@mark.parametrize("index", [None, 2]) | ||
def test_error_found_if_properties_have_multiple_blank_fields(index): | ||
"""Should find an error if properties contain multiple blank fields.""" | ||
properties = {"name": "", "tags": []} | ||
|
||
errors = check_fields_not_blank(properties, FIELDS, index) | ||
|
||
assert len(errors) == 2 | ||
assert all(error.validator == "blank" for error in errors) | ||
assert any( | ||
error.json_path == get_json_path_to_resource_field("name", index) | ||
for error in errors | ||
) | ||
assert any( | ||
error.json_path == get_json_path_to_resource_field("tags", index) | ||
for error in errors | ||
) |
This file contains 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 |
---|---|---|
@@ -0,0 +1,57 @@ | ||
from pytest import mark | ||
|
||
from seedcase_sprout.core.checks.required_fields import RequiredFieldType | ||
from seedcase_sprout.core.sprout_checks.check_fields_present import ( | ||
check_fields_present, | ||
) | ||
from seedcase_sprout.core.sprout_checks.get_json_path_to_resource_field import ( | ||
get_json_path_to_resource_field, | ||
) | ||
|
||
REQUIRED_FIELDS = {"name": RequiredFieldType.str, "tags": RequiredFieldType.list} | ||
|
||
|
||
@mark.parametrize("index", [None, 2]) | ||
def test_no_error_found_in_properties_with_required_fields(index): | ||
"""Should pass properties with required fields present and populated.""" | ||
properties = {"name": "My name", "tags": ["a", "b"]} | ||
|
||
assert check_fields_present(properties, REQUIRED_FIELDS, index) == [] | ||
|
||
|
||
@mark.parametrize("index", [None, 2]) | ||
def test_no_error_found_in_properties_with_required_fields_blank(index): | ||
"""Should pass properties with required fields present but blank.""" | ||
properties = {"name": "", "tags": []} | ||
|
||
assert check_fields_present(properties, REQUIRED_FIELDS, index) == [] | ||
|
||
|
||
@mark.parametrize("index", [None, 2]) | ||
def test_error_found_if_there_is_a_missing_required_field(index): | ||
"""Should find an error if there is a missing required field.""" | ||
properties = {"name": "My name"} | ||
|
||
errors = check_fields_present(properties, REQUIRED_FIELDS, index) | ||
|
||
assert len(errors) == 1 | ||
assert "required" in errors[0].message | ||
assert errors[0].json_path == get_json_path_to_resource_field("tags", index) | ||
assert errors[0].validator == "required" | ||
|
||
|
||
@mark.parametrize("index", [None, 2]) | ||
def test_error_found_if_there_are_multiple_missing_required_fields(index): | ||
"""Should find an error if there are multiple missing required fields.""" | ||
errors = check_fields_present({}, REQUIRED_FIELDS, index) | ||
|
||
assert len(errors) == 2 | ||
assert all(error.validator == "required" for error in errors) | ||
assert any( | ||
error.json_path == get_json_path_to_resource_field("name", index) | ||
for error in errors | ||
) | ||
assert any( | ||
error.json_path == get_json_path_to_resource_field("tags", index) | ||
for error in errors | ||
) |
74 changes: 74 additions & 0 deletions
74
tests/core/sprout_checks/test_check_list_item_field_not_blank.py
This file contains 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 |
---|---|---|
@@ -0,0 +1,74 @@ | ||
from pytest import mark | ||
|
||
from seedcase_sprout.core.checks.required_fields import RequiredFieldType | ||
from seedcase_sprout.core.sprout_checks.check_list_item_field_not_blank import ( | ||
check_list_item_field_not_blank, | ||
) | ||
from seedcase_sprout.core.sprout_checks.get_blank_value_for_type import ( | ||
get_blank_value_for_type, | ||
) | ||
|
||
|
||
def test_no_error_found_in_properties_without_list(): | ||
"""Should pass if the properties do not contain the specified list.""" | ||
assert check_list_item_field_not_blank({}, "items", "field") == [] | ||
|
||
|
||
@mark.parametrize("items", [[], [{}, {}], [{"a": 1}, {"a": 2}]]) | ||
def test_no_error_found_when_list_does_not_contain_field(items): | ||
"""Should pass if list items do not contain the field.""" | ||
properties = {"items": items} | ||
|
||
assert check_list_item_field_not_blank(properties, "items", "field") == [] | ||
|
||
|
||
def test_no_error_found_when_fields_populated(): | ||
"""Should pass if all fields are populated.""" | ||
properties = {"items": [{"field": "value"}, {"field": "value"}]} | ||
|
||
assert check_list_item_field_not_blank(properties, "items", "field") == [] | ||
|
||
|
||
def test_no_error_found_when_fields_are_of_wrong_type(): | ||
"""Should pass if the fields are present but of the wrong type.""" | ||
properties = {"items": [{"field": "value"}, {"field": ""}]} | ||
|
||
assert ( | ||
check_list_item_field_not_blank( | ||
properties, "items", "field", RequiredFieldType.list | ||
) | ||
== [] | ||
) | ||
|
||
|
||
@mark.parametrize( | ||
"field_type,value", | ||
[(RequiredFieldType.str, "value"), (RequiredFieldType.list, [1])], | ||
) | ||
def test_error_found_if_an_item_has_a_blank_field(field_type, value): | ||
"""Should find an error if there is an item with a blank field.""" | ||
properties = { | ||
"items": [{"field": value}, {"field": get_blank_value_for_type(field_type)}] | ||
} | ||
|
||
errors = check_list_item_field_not_blank(properties, "items", "field", field_type) | ||
|
||
assert len(errors) == 1 | ||
assert "blank" in errors[0].message | ||
assert errors[0].json_path == "$.items[1].field" | ||
assert errors[0].validator == "blank" | ||
|
||
|
||
@mark.parametrize("field_type", RequiredFieldType) | ||
def test_error_found_if_multiple_items_have_a_blank_field(field_type): | ||
"""Should find an error if there are multiple items with a blank field.""" | ||
properties = {"items": [{"field": get_blank_value_for_type(field_type)}] * 2} | ||
|
||
errors = check_list_item_field_not_blank(properties, "items", "field", field_type) | ||
|
||
assert len(errors) == 2 | ||
assert all( | ||
"blank" in error.message and error.validator == "blank" for error in errors | ||
) | ||
assert any(error.json_path == "$.items[0].field" for error in errors) | ||
assert any(error.json_path == "$.items[1].field" for error in errors) |
Oops, something went wrong.