From a003313ef9ca44936c03c8b5e92c9ee147319aab Mon Sep 17 00:00:00 2001 From: Anthony Galassi <28850131+bendhouseart@users.noreply.github.com> Date: Tue, 24 Jun 2025 16:38:30 -0400 Subject: [PATCH 1/5] updated models to support background images This was mainly added so that a background image could be applied to a protocol/input for the recently added cavas attribute at https://github.com/ReproNim/reproschema-ui/pull/363 Potentially allows for background use in other activities b/c it was easier to minimally implement and/or one could add a background image/logo or something for their own protocol. --- reproschema/cli.py | 6 ++ reproschema/models/model.py | 19 +++++- reproschema/models/tests/test_schema.py | 86 +++++++++++++++++++++++++ 3 files changed, 110 insertions(+), 1 deletion(-) diff --git a/reproschema/cli.py b/reproschema/cli.py index 7ec52c4..4b82871 100644 --- a/reproschema/cli.py +++ b/reproschema/cli.py @@ -302,3 +302,9 @@ def reproschema2fhir(reproschema_questionnaire, output): with open(output_path / f"{file_name}/{file_name}.json", "w+") as f: f.write(json.dumps(fhir_questionnaire)) + + +if __name__ == "__main__": + import sys + + main(prog_name="reproschema") diff --git a/reproschema/models/model.py b/reproschema/models/model.py index 1dbc067..a85035b 100644 --- a/reproschema/models/model.py +++ b/reproschema/models/model.py @@ -10,7 +10,13 @@ from pydantic.version import VERSION as PYDANTIC_VERSION if int(PYDANTIC_VERSION[0]) >= 2: - from pydantic import BaseModel, ConfigDict, Field, field_validator + from pydantic import ( + BaseModel, + ConfigDict, + Field, + field_validator, + model_validator, + ) else: from pydantic import BaseModel, Field, validator metamodel_version = "None" @@ -178,6 +184,9 @@ class AdditionalProperty(Thing): An object to describe the various properties added to assessments and Items. """ + # Override parent's extra="forbid" to allow extra fields + model_config = ConfigDict(extra="allow") + allow: Optional[List[AllowedType]] = Field( default_factory=list, title="allow", @@ -229,6 +238,14 @@ class AdditionalProperty(Thing): title="UI", description="An element to control UI specifications. Originally @nest in jsonld, but using a class in the model.", ) + + # Add backgroundImage as an explicit field to allow it + backgroundImage: Optional[str] = Field( + None, + title="backgroundImage", + description="Background image for drawing activities.", + ) + id: Optional[str] = Field( None, description="A unique identifier for an entity. Must be either a CURIE shorthand for a URI or a complete URI.", diff --git a/reproschema/models/tests/test_schema.py b/reproschema/models/tests/test_schema.py index f7ee126..501982a 100644 --- a/reproschema/models/tests/test_schema.py +++ b/reproschema/models/tests/test_schema.py @@ -4,6 +4,7 @@ import pytest from pyld import jsonld +from pydantic import ValidationError from ...jsonldutils import load_file from ...utils import start_server, stop_server @@ -176,3 +177,88 @@ def test_item(tmp_path, server_http_kwargs): ) del data_comp["@context"] assert item_dict == data_comp + + +def test_canvas_activity_allows_extra_fields(): + """Test that canvas activities allow backgroundImage (now an explicit field).""" + activity_dict = { + "category": "Activity", + "id": "canvas_activity.jsonld", + "prefLabel": {"en": "Canvas Drawing Activity"}, + "description": {"en": "A canvas drawing activity"}, + "schemaVersion": "1.0.0-rc4", + "version": "0.0.1", + "ui": { + "inputType": "canvas", + "addProperties": [ + { + "isAbout": "item1", + "variableName": "canvas_item", + "backgroundImage": "./images/drawaclock.png", + } + ], + }, + } + + # This should work fine + activity_obj = Activity(**activity_dict) + assert ( + activity_obj.ui.addProperties[0].backgroundImage + == "./images/drawaclock.png" + ) + + +def test_background_image_always_allowed(): + """Test that backgroundImage is always allowed regardless of inputType.""" + activity_dict = { + "category": "Activity", + "id": "radio_activity_with_background.jsonld", + "prefLabel": {"en": "Radio Button Activity with Background"}, + "description": {"en": "A radio button activity with background image"}, + "schemaVersion": "1.0.0-rc4", + "version": "0.0.1", + "ui": { + "inputType": "radio", + "addProperties": [ + { + "isAbout": "item1", + "variableName": "radio_item", + # backgroundImage should be allowed for all activities + "backgroundImage": "./images/some_image.png", + } + ], + }, + } + + # This should work fine - backgroundImage is now an explicit field + activity_obj = Activity(**activity_dict) + assert ( + activity_obj.ui.addProperties[0].backgroundImage + == "./images/some_image.png" + ) + + +def test_activity_without_extra_fields_works(): + """Test that activities without extra fields work normally regardless of input type.""" + activity_dict = { + "category": "Activity", + "id": "normal_activity.jsonld", + "prefLabel": {"en": "Normal Activity"}, + "description": {"en": "A normal activity without extra fields"}, + "schemaVersion": "1.0.0-rc4", + "version": "0.0.1", + "ui": { + "inputType": "radio", + "addProperties": [ + { + "isAbout": "item1", + "variableName": "normal_item", + # No extra fields + } + ], + }, + } + + # This should work fine + activity_obj = Activity(**activity_dict) + assert activity_obj.ui.addProperties[0].variableName == "normal_item" From 1f109fcc47aac4abbe4390665190c64bee4f69dd Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 24 Jun 2025 20:42:56 +0000 Subject: [PATCH 2/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- reproschema/models/tests/test_schema.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reproschema/models/tests/test_schema.py b/reproschema/models/tests/test_schema.py index 501982a..7716fef 100644 --- a/reproschema/models/tests/test_schema.py +++ b/reproschema/models/tests/test_schema.py @@ -3,8 +3,8 @@ from pathlib import Path import pytest -from pyld import jsonld from pydantic import ValidationError +from pyld import jsonld from ...jsonldutils import load_file from ...utils import start_server, stop_server From 3901eabfb14c94f80fa88a45bb821c9c663edfde Mon Sep 17 00:00:00 2001 From: Anthony Galassi <28850131+bendhouseart@users.noreply.github.com> Date: Tue, 24 Jun 2025 16:49:35 -0400 Subject: [PATCH 3/5] make it more restrictive --- reproschema/models/model.py | 17 ++++++++++++++++ reproschema/models/tests/test_schema.py | 27 +++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/reproschema/models/model.py b/reproschema/models/model.py index a85035b..d5d465a 100644 --- a/reproschema/models/model.py +++ b/reproschema/models/model.py @@ -246,6 +246,23 @@ class AdditionalProperty(Thing): description="Background image for drawing activities.", ) + @model_validator(mode="after") + def validate_only_background_image_extra(self): + """Validate that only backgroundImage is allowed as an extra field.""" + # Get any extra fields that weren't explicitly defined + extra_fields = getattr(self, "__pydantic_extra__", {}) + + if extra_fields: + # Since backgroundImage is now an explicit field, any extra fields are not allowed + extra_field_names = list(extra_fields.keys()) + raise ValueError( + f"Extra fields are not permitted in AdditionalProperty. " + f"Only 'backgroundImage' is allowed as an additional field, " + f"but found: {extra_field_names}" + ) + + return self + id: Optional[str] = Field( None, description="A unique identifier for an entity. Must be either a CURIE shorthand for a URI or a complete URI.", diff --git a/reproschema/models/tests/test_schema.py b/reproschema/models/tests/test_schema.py index 501982a..afabb8b 100644 --- a/reproschema/models/tests/test_schema.py +++ b/reproschema/models/tests/test_schema.py @@ -238,6 +238,33 @@ def test_background_image_always_allowed(): ) +def test_extra_fields_rejected(): + """Test that extra fields other than backgroundImage are rejected.""" + activity_dict = { + "category": "Activity", + "id": "activity_with_invalid_extra.jsonld", + "prefLabel": {"en": "Activity with Invalid Extra Field"}, + "description": {"en": "An activity with an invalid extra field"}, + "schemaVersion": "1.0.0-rc4", + "version": "0.0.1", + "ui": { + "inputType": "radio", + "addProperties": [ + { + "isAbout": "item1", + "variableName": "test_item", + "backgroundImage": "./images/test.png", # This is allowed + "customTool": "special_pen", # This should be rejected + } + ], + }, + } + + # This should fail because customTool is not allowed + with pytest.raises(ValueError, match="Extra fields are not permitted"): + Activity(**activity_dict) + + def test_activity_without_extra_fields_works(): """Test that activities without extra fields work normally regardless of input type.""" activity_dict = { From 04b2d5c7f620db5c020286347d593e1682f2def0 Mon Sep 17 00:00:00 2001 From: Anthony Galassi <28850131+bendhouseart@users.noreply.github.com> Date: Tue, 24 Jun 2025 16:54:56 -0400 Subject: [PATCH 4/5] make flake 8 happy --- reproschema/cli.py | 2 -- reproschema/models/model.py | 2 -- 2 files changed, 4 deletions(-) diff --git a/reproschema/cli.py b/reproschema/cli.py index 4b82871..047c362 100644 --- a/reproschema/cli.py +++ b/reproschema/cli.py @@ -305,6 +305,4 @@ def reproschema2fhir(reproschema_questionnaire, output): if __name__ == "__main__": - import sys - main(prog_name="reproschema") diff --git a/reproschema/models/model.py b/reproschema/models/model.py index d5d465a..9489828 100644 --- a/reproschema/models/model.py +++ b/reproschema/models/model.py @@ -1,7 +1,5 @@ from __future__ import annotations -import re -import sys from datetime import date, datetime from decimal import Decimal from enum import Enum From 5ff727e303eac7782afc0d301bcca82f796f98c8 Mon Sep 17 00:00:00 2001 From: Anthony Galassi <28850131+bendhouseart@users.noreply.github.com> Date: Tue, 24 Jun 2025 16:56:11 -0400 Subject: [PATCH 5/5] update flake8 config --- .flake8 | 1 + 1 file changed, 1 insertion(+) diff --git a/.flake8 b/.flake8 index 8d6a197..7333708 100644 --- a/.flake8 +++ b/.flake8 @@ -8,5 +8,6 @@ exclude = docs/tools/ reproschema/_version.py reproschema/models/model.py + *venv* max-line-length=79 extend-ignore = B001, B006, B016, E501, E722, F821