diff --git a/requirements/dev.txt b/requirements/dev.txt index 93fe986551..485c595299 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -363,7 +363,7 @@ django-sessionprofile==2.0.0 # -c requirements/ci.txt # -r requirements/ci.txt # django-digid-eherkenning -django-silk==5.1.0 +django-silk==5.2.0 # via -r requirements/dev.in django-simple-certmanager==2.4.1 # via diff --git a/src/openforms/formio/datastructures.py b/src/openforms/formio/datastructures.py index 848b2855fb..33119ee36d 100644 --- a/src/openforms/formio/datastructures.py +++ b/src/openforms/formio/datastructures.py @@ -1,6 +1,5 @@ import re from collections import UserDict -from collections.abc import Hashable from typing import Iterator, cast from glom import PathAccessError, assign, glom @@ -162,16 +161,42 @@ class FormioData(UserDict): """ data: dict[str, JSONValue] + _keys: set[str] + """ + A collection of flattened key names, for quicker __contains__ access + """ - def __getitem__(self, key: Hashable): + def __init__(self, *args, **kwargs): + self._keys = set() + super().__init__(*args, **kwargs) + + def __getitem__(self, key: str): + if "." not in key: + return self.data[key] return cast(JSONValue, glom(self.data, key)) - def __setitem__(self, key: Hashable, value: JSONValue): + def __setitem__(self, key: str, value: JSONValue): assign(self.data, key, value, missing=dict) + self._keys.add(key) + + def __contains__(self, key: object) -> bool: + """ + Check if the key is present in the data container. + + This gets called via ``formio_data.get(...)`` to check if the default needs to + be returned or not. Keys are expected to be strings taken from ``variable.key`` + fields. + """ + if not isinstance(key, str): + raise TypeError("Only string keys are supported") + + # for direct keys, we can optimize access and bypass glom + its exception + # throwing. + if "." not in key: + return key in self._keys - def __contains__(self, key: Hashable) -> bool: try: self[key] + return True except PathAccessError: return False - return True diff --git a/src/openforms/formio/tests/test_datastructures.py b/src/openforms/formio/tests/test_datastructures.py index 5d82629a9f..374380006a 100644 --- a/src/openforms/formio/tests/test_datastructures.py +++ b/src/openforms/formio/tests/test_datastructures.py @@ -89,6 +89,20 @@ def test_initializing_with_dotted_paths_expands(self): self.assertEqual(formio_data, expected) + def test_key_access_must_be_string(self): + formio_data = FormioData({"foo": "bar"}) + + bad_keys = ( + 3, + None, + 4.3, + ("foo",), + ) + + for key in bad_keys: + with self.assertRaises(TypeError): + key in formio_data # type: ignore + class FormioConfigurationWrapperTests(TestCase): diff --git a/src/openforms/registrations/contrib/objects_api/submission_registration.py b/src/openforms/registrations/contrib/objects_api/submission_registration.py index 4523575702..7734035e1a 100644 --- a/src/openforms/registrations/contrib/objects_api/submission_registration.py +++ b/src/openforms/registrations/contrib/objects_api/submission_registration.py @@ -9,7 +9,6 @@ from django.db.models import F import glom -from glom import PathAccessError from openforms.authentication.service import AuthAttribute from openforms.contrib.objects_api.clients import ( @@ -537,7 +536,7 @@ def get_record_data( for key, variable in state.variables.items(): try: submission_value = dynamic_values[key] - except PathAccessError: + except KeyError: continue # special casing documents - we transform the formio file upload data into