Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1934 +/- ##
==========================================
+ Coverage 99.05% 99.08% +0.02%
==========================================
Files 314 314
Lines 11778 11801 +23
==========================================
+ Hits 11667 11693 +26
+ Misses 111 108 -3 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
There isn't really anything wrong with the implementation but couldn't the same thing be done with a local function? def load_data(file: str = DEFAULT) -> MyModel:
return load_json_file_to_class(MyModel, file)
bc.plans.my_plan(det1, load_data())
bc.plans.my_plan(det1, load_data("path/to/data.json"))As models are going to be serialized back to JSON and then reconstructed and validated on the server, it might be easier to read straight to JSON and pass it directly. At the moment the data is going def load_data(file: str = DEFAULT):
with open(file) as src:
return json.load(src)I might have missed something though, is there a use case within plans where this would offer something more? |
Yes, I could do this def p60_load_sequence(file: str) -> P60Sequence:
return load_json_file_to_class(P60Sequence, file)
...
def i09_load_sequence(file: str) -> I09Sequence:
return load_json_file_to_class(I09Sequence, file)
...
def i09_1_load_sequence(file: str) -> I091Sequence:
return load_json_file_to_class(I091Sequence, file)
...
def b07_load_sequence(file: str) -> B07BSequence:
return load_json_file_to_class(B07BSequence, file)
...
def b07_1_load_sequence(file: str) -> B07CSequence:
return load_json_file_to_class(B07CSequence, file)for each specific beamline. However, I wanted a standard way to do this via configuration rather defining a new function in the environment every time. This also means it is fully tested, has documentation, and can also reused with existing tests within dodal and plan repos rather than redefining a new loader function for each test.
So I know the type checking isn't added yet, but when it is added surely this will provide the type checking for the model? E.g the model on client side and server side have to match? def my_plan(model: MyModel1, ...):
...
load_model1 = JsonModelLoader[MyModel1](MyModel1)
load_model2 = JsonModelLoader[MyModel2](MyModel2)
...
bc.plans.my_plan(load_model1("/my/file")) # Okay
bc.plans.my_plan(load_model2("/my/file")) # Static type checker should complain as it isn't using the correct model? |
Unfortunately not. The client is working entirely from the API schema provided by the server. For a plan that accepts You can see the spec the client has available by running |
It's not really any different but if you want to avoid writing a function you could do def json_model_loader(model: type[TBaseModel], default_file: str|None=None):
def load_json(file: str| None = default_file) -> TBaseModel:
if file is None:
raise ValueError("No filename given")
return load_json_file_to_class(bm, file)
return load_jsonand then your example becomes p60_load_sequence = json_model_loader(P60Sequence, file)
i09_load_sequence = json_model_loader(I09Sequence, file) which isn't really that different to (and if anything is clearer than) p60_load_sequence = JsonModelLoader[P60Sequence](P60Sequence, "my/default/file.json")
i09_load_sequence = JsonModelLoader[I09Sequence](I09Sequence, "my/default/file.json") |
With a small tweak, I do like how simple your function is def json_model_loader(model: type[TBaseModel], default_file: str | None = None):
def load_json(file: str | None = None) -> TBaseModel:
if file is None and default_file is not None:
return load_json_file_to_class(model, default_file)
elif file is not None:
return load_json_file_to_class(model, file)
raise RuntimeError(
f"{model.__name__} loader has no default file configured and no file was"
"provided when trying to load in a model. "
)
return load_jsonHowever, I am not sure it is correct approach because it is quite limiting. For example, I cannot view or update the default file at a later date, I have to rerun the factory function again and reassign to the variable for my function which I'm not sure I like |
|
When would you need to check or modify the default value? Isn't the point that you can override it if you want a different value? Also, I posted my example function without checking it properly, I think if you use def json_model_loader(model: type[TBaseModel], default_file: str|None=None):
def load_json(file: str| None = default_file) -> TBaseModel:
if file is None:
raise ValueError("No filename given")
return load_json_file_to_class(bm, file)
return load_jsonyou get the default file behaviour you're after. |
I was thinking of just trying to make it flexible enough. However, if we need it we can cross that bridge when we get there. I have update it to use your implementation so ready for review |
Create a
json_model_loaderfactory function. Wrapsload_json_file_to_classbut allows for use of configuring defaults. Will be used with plans and BlueapiClient.Instructions to reviewer on how to test:
Checks for reviewer
dodal connect ${BEAMLINE}