Skip to content

Commit b6ca46f

Browse files
author
piptouque
committed
🐛(models) fix allow xapi Extensions with empty string values
When defined with a bare Dict[...] as a field in a pydantic class, Extensions inherit from the config which disallows empty strings in dict values. This, however, should be allowed according to spec: https://github.com/adlnet/xAPI-Spec/blob/master/xAPI-Data.md#requirements-18 (An LRS MUST NOT reject a Statement based on the values of the extensions map). We must override base the model_config for our extensions.
1 parent 3b37968 commit b6ca46f

3 files changed

Lines changed: 31 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ and this project adheres to
2626
`test-helm` CI job to fix flaky "no matching resources"/"status not found"
2727
- Drop the stale `add_ssh_keys` entry from the `deploy-docs` CI job so it
2828
pushes to `gh-pages` with the read-write checkout key
29+
- Fix XAPI definitions extensions not accepting empty strings as values.
30+
31+
### Changed
32+
33+
- Refactor statements' ExtensionMap
2934

3035
## [5.0.1] - 2024-07-11
3136

src/ralph/models/xapi/base/common.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
"""Common for xAPI base definitions."""
22

3-
from typing import Dict, Type, Union
3+
from typing import Dict, Type, Union, Annotated
44

55
from langcodes import tag_is_valid
6-
from pydantic import RootModel, model_validator, validate_email
6+
from pydantic import ConfigDict, RootModel, StringConstraints, model_validator, validate_email
77
from rfc3987 import parse
88

99
from ralph.conf import NonEmptyStrictStr
@@ -46,8 +46,10 @@ def validate_language_tag(cls, tag):
4646

4747
LanguageMap = Dict[LanguageTag, NonEmptyStrictStr]
4848

49-
ExtensionMap = Dict[IRI, Union[str, int, bool, list, dict, None]]
49+
ExtensionValue = Union[Annotated[str, StringConstraints(min_length=0)], bool, int, list, dict, None]
5050

51+
class ExtensionMap(RootModel[Dict[IRI, ExtensionValue]]):
52+
"""Pydantic custom data type for XAPI context and object definitions extensions."""
5153

5254
class MailtoEmail(RootModel[str]):
5355
"""Pydantic custom data type validating `mailto:email` format."""

tests/models/xapi/base/test_common.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,6 @@ class DummyLanguageTagModel(BaseModel):
143143
[
144144
({"extensions": {}}),
145145
({"extensions": {"http://localhost/foo/bar": None}}),
146-
({"extensions": {"http://localhost/foo/bar": None}}),
147146
({"extensions": {"http://localhost/foo/bar": 42}}),
148147
({"extensions": {"http://localhost/foo/bar": []}}),
149148
({"extensions": {"http://localhost/foo/bar": {}}}),
@@ -156,6 +155,26 @@ class DummyLanguageTagModel(BaseModel):
156155
}
157156
}
158157
),
158+
(
159+
{
160+
"extensions": {
161+
"http://localhost/foo/bar": {
162+
"http://localhost/food/bard": "An explanation",
163+
"whatever": "that_is",
164+
},
165+
}
166+
}
167+
),
168+
(
169+
{
170+
"extensions": {
171+
"http://localhost/foo/bar": {
172+
"http://localhost/food/bard": "An explanation",
173+
"nope": "",
174+
},
175+
}
176+
}
177+
),
159178
],
160179
)
161180
def test_models_xapi_base_common_field_extensions_with_valid_data(values):
@@ -181,6 +200,7 @@ class DummyExtensionsModel(BaseModel):
181200
"extensions\n Input should be a valid dictionary",
182201
),
183202
({"extensions": {"localhost": 42}}, ValidationError, "not a valid 'IRI'"),
203+
({"extensions": {"": 43}}, ValidationError, "not a valid 'IRI'"),
184204
],
185205
)
186206
def test_models_xapi_base_common_field_extensions_with_invalid_data(

0 commit comments

Comments
 (0)