diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3a43116e3..dd70792ff 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,13 @@ dcicutils Change Log ---------- +8.11.0 +====== + +* Add more schema parsing functions to `schema_utils`, including for new properties for + generating submission templates + + 8.10.0 ====== diff --git a/dcicutils/schema_utils.py b/dcicutils/schema_utils.py index 82aa6bb12..b299b9395 100644 --- a/dcicutils/schema_utils.py +++ b/dcicutils/schema_utils.py @@ -1,5 +1,6 @@ import os from typing import Any, Dict, List, Optional, Tuple + from dcicutils.misc_utils import to_camel_case @@ -8,6 +9,7 @@ class JsonSchemaConstants: ARRAY = "array" BOOLEAN = "boolean" DEFAULT = "default" + DEPENDENT_REQUIRED = "dependentRequired" ENUM = "enum" FORMAT = "format" INTEGER = "integer" @@ -29,6 +31,10 @@ class EncodedSchemaConstants: LINK_TO = "linkTo" MERGE_REF = "$merge" MIXIN_PROPERTIES = "mixinProperties" + SUBMISSION_COMMENT = "submissionComment" + SUBMISSION_EXAMPLES = "submissionExamples" + SUBMITTER_REQUIRED = "submitterRequired" + SUGGESTED_ENUM = "suggested_enum" UNIQUE_KEY = "uniqueKey" @@ -203,6 +209,50 @@ def get_description(schema: Dict[str, Any]) -> str: return schema.get(SchemaConstants.DESCRIPTION, "") +def is_submitter_required(schema: Dict[str, Any]) -> bool: + """Return True if the schema is marked as required for submitters. + + Specifically, required for external (i.e. non-admin) submitters. + + This is typically validated within the context of a oneOf, anyOf, + or allOf schema on an item type which is used within the team and + by external submitters, and is tricky to pick up on automatically. + """ + return schema.get(SchemaConstants.SUBMITTER_REQUIRED, False) + + +def get_submission_comment(schema: Dict[str, Any]) -> str: + """Return the submission comment for a property. + + Custom property that can be manually added to a schema to provide + additional context for submitters. + """ + return schema.get(SchemaConstants.SUBMISSION_COMMENT, "") + + +def get_submission_examples(schema: Dict[str, Any]) -> List[str]: + """Return the submission example for a property. + + Custom property that can be manually added to a schema to provide + an example for submitters. + """ + return schema.get(SchemaConstants.SUBMISSION_EXAMPLES, []) + + +def get_suggested_enum(schema: Dict[str, Any]) -> List[str]: + """Return the suggested enum for a property. + + Custom property that can be manually added to a schema to provide + a suggested list of values for submitters. + """ + return schema.get(SchemaConstants.SUGGESTED_ENUM, []) + + +def get_dependent_required(schema: Dict[str, Any]) -> Dict[str, List[str]]: + """Return the dependent required properties of a schema.""" + return schema.get(SchemaConstants.DEPENDENT_REQUIRED, {}) + + class Schema: def __init__(self, schema: dict, type: Optional[str] = None) -> None: diff --git a/pyproject.toml b/pyproject.toml index 4ea9c75b4..315511b2d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "dcicutils" -version = "8.10.0" +version = "8.11.0" description = "Utility package for interacting with the 4DN Data Portal and other 4DN resources" authors = ["4DN-DCIC Team "] license = "MIT" diff --git a/test/test_schema_utils.py b/test/test_schema_utils.py index 8a928ee3b..151d95651 100644 --- a/test/test_schema_utils.py +++ b/test/test_schema_utils.py @@ -31,6 +31,10 @@ }, "fun": {"type": "array", "items": {"type": "string"}}, } +DEPENDENT_REQUIRED = { + "bar": ["baz"], + "foo": ["fu"], +} SCHEMA = { "required": REQUIRED, "anyOf": ANY_OF, @@ -38,11 +42,14 @@ "identifyingProperties": IDENTIFYING_PROPERTIES, "mixinProperties": MIXIN_PROPERTIES, "properties": PROPERTIES, + "dependentRequired": DEPENDENT_REQUIRED, } FORMAT = "email" PATTERN = "some_regex" ENUM = ["foo", "bar"] DESCRIPTION = "foo" +COMMENT = "bar" +EXAMPLE = ENUM STRING_SCHEMA = { "type": "string", "format": FORMAT, @@ -50,6 +57,10 @@ "linkTo": "foo", "enum": ENUM, "description": DESCRIPTION, + "submitterRequired": True, + "submissionComment": COMMENT, + "submissionExamples": EXAMPLE, + "suggested_enum": ENUM, } ARRAY_SCHEMA = {"type": "array", "items": [STRING_SCHEMA]} OBJECT_SCHEMA = {"type": "object", "properties": {"foo": STRING_SCHEMA}} @@ -363,3 +374,61 @@ def test_get_enum(schema: Dict[str, Any], expected: List[str]) -> None: ) def test_get_description(schema: Dict[str, Any], expected: str) -> None: assert schema_utils.get_description(schema) == expected + + +@pytest.mark.parametrize( + "schema,expected", + [ + ({}, False), + (NUMBER_SCHEMA, False), + (STRING_SCHEMA, True), + ], +) +def test_is_submitter_required(schema: Dict[str, Any], expected: bool) -> None: + assert schema_utils.is_submitter_required(schema) == expected + + +@pytest.mark.parametrize( + "schema,expected", + [ + ({}, ""), + (STRING_SCHEMA, COMMENT), + ], +) +def test_get_submission_comment(schema: Dict[str, Any], expected: str) -> None: + assert schema_utils.get_submission_comment(schema) == expected + + +@pytest.mark.parametrize( + "schema,expected", + [ + ({}, []), + (STRING_SCHEMA, EXAMPLE), + ], +) +def test_get_submission_examples(schema: Dict[str, Any], expected: List[str]) -> None: + assert schema_utils.get_submission_examples(schema) == expected + + +@pytest.mark.parametrize( + "schema,expected", + [ + ({}, []), + (STRING_SCHEMA, ENUM), + ], +) +def test_get_suggested_enum(schema: Dict[str, Any], expected: List[str]) -> None: + assert schema_utils.get_suggested_enum(schema) == expected + + +@pytest.mark.parametrize( + "schema,expected", + [ + ({}, {}), + (SCHEMA, DEPENDENT_REQUIRED), + ], +) +def test_get_dependent_required( + schema: Dict[str, Any], expected: Dict[str, List[str]] +) -> None: + assert schema_utils.get_dependent_required(schema) == expected