Skip to content

Commit 75448f0

Browse files
committed
Validate assignment to BaseProperty.constraints
I've upgraded this to a property, and added basic validation to check the dictionary keys.
1 parent fc52f69 commit 75448f0

2 files changed

Lines changed: 42 additions & 16 deletions

File tree

src/labthings_fastapi/properties.py

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -351,27 +351,43 @@ def __init__(self, constraints: Mapping[str, Any] | None = None) -> None:
351351
super().__init__()
352352
self._model: type[BaseModel] | None = None
353353
self.readonly: bool = False
354-
self.constraints = constraints or {}
355-
for key in self.constraints:
354+
self._constraints = {}
355+
try:
356+
self.constraints = constraints or {}
357+
except UnsupportedConstraintError:
358+
raise
359+
360+
@builtins.property
361+
def constraints(self) -> Mapping[str, Any]: # noqa[DOC201]
362+
"""Validation constraints applied to this property.
363+
364+
This mapping contains keyword arguments that will be passed to
365+
`pydantic.Field` to add validation constraints to the property.
366+
See `pydantic.Field` for details. The module-level constant
367+
`CONSTRAINT_ARGS` lists the supported constraint arguments.
368+
369+
Note that these constraints will be enforced when values are
370+
received over HTTP, but they are not automatically enforced
371+
when setting the property directly on the `.Thing` instance
372+
from Python code.
373+
"""
374+
return self._constraints
375+
376+
@constraints.setter
377+
def constraints(self, new_constraints: Mapping[str, Any]) -> None:
378+
r"""Set the constraints added to the model.
379+
380+
:param new_constraints: the new value of ``constraints``\ .
381+
382+
:raises UnsupportedConstraintError: if invalid dictionary keys are present.
383+
"""
384+
for key in new_constraints:
356385
if key not in CONSTRAINT_ARGS:
357386
raise UnsupportedConstraintError(
358387
f"Unknown constraint argument: {key}. \n"
359388
f"Supported arguments are: {', '.join(CONSTRAINT_ARGS)}."
360389
)
361-
362-
constraints: Mapping[str, Any]
363-
"""Validation constraints applied to this property.
364-
365-
This mapping contains keyword arguments that will be passed to
366-
`pydantic.Field` to add validation constraints to the property.
367-
See `pydantic.Field` for details. The module-level constant
368-
`CONSTRAINT_ARGS` lists the supported constraint arguments.
369-
370-
Note that these constraints will be enforced when values are
371-
received over HTTP, but they are not automatically enforced
372-
when setting the property directly on the `.Thing` instance
373-
from Python code.
374-
"""
390+
self._constraints = new_constraints
375391

376392
@builtins.property
377393
def model(self) -> type[BaseModel]:

tests/test_properties.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,16 @@ class AnotherBadConstraintThing(lt.Thing):
456456
# as metadata if used on the wrong type. We don't currently raise errors
457457
# for these.
458458

459+
# We should also raise errors if constraints are set after a property is defined
460+
with pytest.raises(UnsupportedConstraintError):
461+
462+
class FunctionalBadConstraintThing(lt.Thing):
463+
@lt.property
464+
def functional_bad_prop(self) -> str:
465+
return "foo"
466+
467+
functional_bad_prop.constraints = {"bad_constraint": 2}
468+
459469

460470
def test_propertyinfo():
461471
"""Check the PropertyInfo class is generated correctly."""

0 commit comments

Comments
 (0)