-
Notifications
You must be signed in to change notification settings - Fork 2
Description
Problem
Some @lt.thing_action
methods may modify Thing Settings indirectly (without calling the setter). These need to call self.save_settings()
manually. Currently, there's no clean way to automatically save settings after a thing action completes without breaking dependency injection.
Current Workaround
@lt.thing_action
def set_background(self, portal: lt.deps.BlockingPortal):
"""Action that modifies settings indirectly."""
# ... action logic that modifies thing_setting data ...
self.save_settings() # Manual call required
Attempted Solution That Failed
I tried creating a decorator to handle this automatically:
import functools
def modifies_settings(func):
"""Save settings after function completes, for methods that modify Thing Settings."""
@functools.wraps(func)
def action_wrapper(*args, **kwargs):
result = func(*args, **kwargs)
# args[0] is self as this must be a method of a thing action.
args[0].save_settings()
return result
return action_wrapper
then
@lt.thing_action
@modifies_settings
def set_background(self, portal: lt.deps.BlockingPortal):
# ... action logic ...
This fails at labthings_fastapi/utilities/introspection.py", line 101, in input_model_from_signature
with a number of Pydantic _generate_schema.py"
calls
Proposed Solution
Add an optional save_settings
parameter to the @thing_action decorator
:
@lt.thing_action(save_settings=True)
def set_background(self, portal: lt.deps.BlockingPortal):
"""Action that modifies settings indirectly."""
# ... action logic that modifies thing_setting data ...
# save_settings() called automatically after successful completion
This would be particularly useful for any action that indirectly modifies @thing_setting properties through complex logic. But would be cleaner than relying on save_settings()
being called manually, without changing any existing behaviour.
Full log from the decorator attempt
LabThings Could't Load
Something went wrong when setting up your LabThings server.
Please check your configuration and try again.
More details may be shown below:
Unable to generate pydantic-core schema for . Set `arbitrary_types_allowed=True` in the model_config to ignore this error or implement `__get_pydantic_core_schema__` on your type to fully support it.
If you got this error by calling handler() within `__get_pydantic_core_schema__` then you likely need to call `handler.generate_schema()` since we do not call `__get_pydantic_core_schema__` on `` otherwise to avoid infinite recursion.
For further information visit https://errors.pydantic.dev/2.10/u/schema-for-unknown-type
The following Things loaded successfully:
Your configuration:
{
"things": {
"/camera/": "openflexure_microscope_server.things.camera.simulation:SimulatedCamera",
"/stage/": "openflexure_microscope_server.things.stage.dummy:DummyStage",
"/auto_recentre_stage/": "openflexure_microscope_server.things.auto_recentre_stage:RecentringThing",
"/autofocus/": "openflexure_microscope_server.things.autofocus:AutofocusThing",
"/camera_stage_mapping/": "openflexure_microscope_server.things.camera_stage_mapping:CameraStageMapper",
"/system/": "openflexure_microscope_server.things.system:OpenFlexureSystem",
"/smart_scan/": {
"class": "openflexure_microscope_server.things.smart_scan:SmartScanThing",
"kwargs": {
"scans_folder": "./openflexure/scans/"
}
}
},
"settings_folder": "./openflexure/settings/",
"log_folder": "./openflexure/logs/"
}
Traceback
Traceback (most recent call last):
File "/home/js3214/Documents/gitstuffs/openflexure-microscope-server/src/openflexure_microscope_server/server/__init__.py", line 85, in serve_from_cli
server = lt.cli.server_from_config(config)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/js3214/Documents/gitstuffs/openflexure-microscope-server/.venv/lib/python3.11/site-packages/labthings_fastapi/server/__init__.py", line 172, in server_from_config
cls = object_reference_to_object(thing["class"])
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/js3214/Documents/gitstuffs/openflexure-microscope-server/.venv/lib/python3.11/site-packages/labthings_fastapi/utilities/object_reference_to_object.py", line 15, in object_reference_to_object
obj = importlib.import_module(modname)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.11/importlib/__init__.py", line 126, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "", line 1204, in _gcd_import
File "", line 1176, in _find_and_load
File "", line 1126, in _find_and_load_unlocked
File "", line 241, in _call_with_frames_removed
File "", line 1204, in _gcd_import
File "", line 1176, in _find_and_load
File "", line 1147, in _find_and_load_unlocked
File "", line 690, in _load_unlocked
File "", line 940, in exec_module
File "", line 241, in _call_with_frames_removed
File "/home/js3214/Documents/gitstuffs/openflexure-microscope-server/src/openflexure_microscope_server/things/camera/__init__.py", line 157, in
class BaseCamera(lt.Thing):
File "/home/js3214/Documents/gitstuffs/openflexure-microscope-server/src/openflexure_microscope_server/things/camera/__init__.py", line 540, in BaseCamera
@lt.thing_action
^^^^^^^^^^^^^^^
File "/home/js3214/Documents/gitstuffs/openflexure-microscope-server/.venv/lib/python3.11/site-packages/labthings_fastapi/decorators/__init__.py", line 70, in thing_action
return mark_thing_action(func, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/js3214/Documents/gitstuffs/openflexure-microscope-server/.venv/lib/python3.11/site-packages/labthings_fastapi/decorators/__init__.py", line 57, in mark_thing_action
return ActionDescriptorSubclass(func, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/js3214/Documents/gitstuffs/openflexure-microscope-server/.venv/lib/python3.11/site-packages/labthings_fastapi/descriptors/action.py", line 70, in __init__
self.input_model = input_model_from_signature(
^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/js3214/Documents/gitstuffs/openflexure-microscope-server/.venv/lib/python3.11/site-packages/labthings_fastapi/utilities/introspection.py", line 101, in input_model_from_signature
model = create_model( # type: ignore[call-overload]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/js3214/Documents/gitstuffs/openflexure-microscope-server/.venv/lib/python3.11/site-packages/pydantic/main.py", line 1678, in create_model
return meta(
^^^^^
File "/home/js3214/Documents/gitstuffs/openflexure-microscope-server/.venv/lib/python3.11/site-packages/pydantic/_internal/_model_construction.py", line 224, in __new__
complete_model_class(
File "/home/js3214/Documents/gitstuffs/openflexure-microscope-server/.venv/lib/python3.11/site-packages/pydantic/_internal/_model_construction.py", line 602, in complete_model_class
schema = cls.__get_pydantic_core_schema__(cls, handler)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/js3214/Documents/gitstuffs/openflexure-microscope-server/.venv/lib/python3.11/site-packages/pydantic/main.py", line 702, in __get_pydantic_core_schema__
return handler(source)
^^^^^^^^^^^^^^^
File "/home/js3214/Documents/gitstuffs/openflexure-microscope-server/.venv/lib/python3.11/site-packages/pydantic/_internal/_schema_generation_shared.py", line 84, in __call__
schema = self._handler(source_type)
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/js3214/Documents/gitstuffs/openflexure-microscope-server/.venv/lib/python3.11/site-packages/pydantic/_internal/_generate_schema.py", line 610, in generate_schema
schema = self._generate_schema_inner(obj)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/js3214/Documents/gitstuffs/openflexure-microscope-server/.venv/lib/python3.11/site-packages/pydantic/_internal/_generate_schema.py", line 879, in _generate_schema_inner
return self._model_schema(obj)
^^^^^^^^^^^^^^^^^^^^^^^
File "/home/js3214/Documents/gitstuffs/openflexure-microscope-server/.venv/lib/python3.11/site-packages/pydantic/_internal/_generate_schema.py", line 691, in _model_schema
{k: self._generate_md_field_schema(k, v, decorators) for k, v in fields.items()},
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/js3214/Documents/gitstuffs/openflexure-microscope-server/.venv/lib/python3.11/site-packages/pydantic/_internal/_generate_schema.py", line 691, in
{k: self._generate_md_field_schema(k, v, decorators) for k, v in fields.items()},
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/js3214/Documents/gitstuffs/openflexure-microscope-server/.venv/lib/python3.11/site-packages/pydantic/_internal/_generate_schema.py", line 1071, in _generate_md_field_schema
common_field = self._common_field_schema(name, field_info, decorators)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/js3214/Documents/gitstuffs/openflexure-microscope-server/.venv/lib/python3.11/site-packages/pydantic/_internal/_generate_schema.py", line 1263, in _common_field_schema
schema = self._apply_annotations(
^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/js3214/Documents/gitstuffs/openflexure-microscope-server/.venv/lib/python3.11/site-packages/pydantic/_internal/_generate_schema.py", line 2056, in _apply_annotations
schema = get_inner_schema(source_type)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/js3214/Documents/gitstuffs/openflexure-microscope-server/.venv/lib/python3.11/site-packages/pydantic/_internal/_schema_generation_shared.py", line 84, in __call__
schema = self._handler(source_type)
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/js3214/Documents/gitstuffs/openflexure-microscope-server/.venv/lib/python3.11/site-packages/pydantic/_internal/_generate_schema.py", line 2131, in new_handler
schema = metadata_get_schema(source, get_inner_schema)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/js3214/Documents/gitstuffs/openflexure-microscope-server/.venv/lib/python3.11/site-packages/pydantic/_internal/_generate_schema.py", line 2127, in
lambda source, handler: handler(source)
^^^^^^^^^^^^^^^
File "/home/js3214/Documents/gitstuffs/openflexure-microscope-server/.venv/lib/python3.11/site-packages/pydantic/_internal/_schema_generation_shared.py", line 84, in __call__
schema = self._handler(source_type)
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/js3214/Documents/gitstuffs/openflexure-microscope-server/.venv/lib/python3.11/site-packages/pydantic/_internal/_generate_schema.py", line 2037, in inner_handler
schema = self._generate_schema_inner(obj)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/js3214/Documents/gitstuffs/openflexure-microscope-server/.venv/lib/python3.11/site-packages/pydantic/_internal/_generate_schema.py", line 884, in _generate_schema_inner
return self.match_type(obj)
^^^^^^^^^^^^^^^^^^^^
File "/home/js3214/Documents/gitstuffs/openflexure-microscope-server/.venv/lib/python3.11/site-packages/pydantic/_internal/_generate_schema.py", line 995, in match_type
return self._unknown_type_schema(obj)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/js3214/Documents/gitstuffs/openflexure-microscope-server/.venv/lib/python3.11/site-packages/pydantic/_internal/_generate_schema.py", line 513, in _unknown_type_schema
raise PydanticSchemaGenerationError(
pydantic.errors.PydanticSchemaGenerationError: Unable to generate pydantic-core schema for . Set `arbitrary_types_allowed=True` in the model_config to ignore this error or implement `__get_pydantic_core_schema__` on your type to fully support it.
If you got this error by calling handler() within `__get_pydantic_core_schema__` then you likely need to call `handler.generate_schema()` since we do not call `__get_pydantic_core_schema__` on `` otherwise to avoid infinite recursion.
For further information visit https://errors.pydantic.dev/2.10/u/schema-for-unknown-type