-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(binding-constraints): create UpdateConstraints command #2274
base: dev
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Once all of this will be handled, we could add small unit tests for this command alone
@@ -141,7 +141,7 @@ def get_binding_constraint_config_cls(study_version: StudyVersion) -> Type[Bindi | |||
return BindingConstraintPropertiesBase | |||
|
|||
|
|||
def create_binding_constraint_config(study_version: StudyVersion, **kwargs: Any) -> BindingConstraintProperties: | |||
def create_binding_constraint_props(study_version: StudyVersion, **kwargs: Any) -> BindingConstraintProperties: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor: Rename create_binding_constraint_properties
@@ -169,9 +169,8 @@ def test_update_multiple_binding_constraints(self, client: TestClient, user_acce | |||
res = client.get(f"/v1/studies/{study_id}/commands") | |||
assert res.status_code == 200 | |||
json_result = res.json() | |||
assert len(json_result) == 2 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change the comments in this test as they are no longer relevant with your change
bcs_output = {} | ||
for bc_id, bc_input in bcs_by_ids.items(): | ||
# check binding constraint id sent by user exist for this study | ||
# Note that this check is both done here and when the commmand is applied as well |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo: command with 2 m :)
# The user changed the operator, we have to rename matrices accordingly | ||
existing_operator = BindingConstraintOperator(current_value["operator"]) | ||
update_matrices_names(file_study, bc_id, existing_operator, value.operator) | ||
# convert payload sent by user (most likely from table mode) to a ConstraintOutput dict |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can remove the comment in-between parenthesis
dict: The adapted binding constraint data in the output format. | ||
|
||
""" | ||
bc_json_copy = copy.deepcopy(bc_json) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure to understand why you need a copy here
|
||
# Properties of the `UPDATE_BINDING_CONSTRAINT` command: | ||
bc_props_by_id: t.Mapping[str, BindingConstraintProperties] | ||
study_version: StudyVersion |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't need this it's already inside the ICommand class
removed_groups = old_groups - new_groups | ||
remove_bc_from_scenario_builder(file_study, removed_groups) | ||
file_study.tree.save(bcs_json, bcs_url) | ||
return CommandOutput(status=True, message="UpdatedBindingVonstraints command created successfully.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo
action=self.command_name.value, args=json_command, version=self.version, study_version=self.study_version | ||
) | ||
|
||
def generate_replacement_matrices( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can put this method private and static
study_version: StudyVersion, | ||
bc_props: BindingConstraintProperties, | ||
operator: BindingConstraintOperator, | ||
) -> t.Iterator[t.Tuple[str, t.Union[t.List[t.List[MatrixData]], str]]]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can type this a t.Iterator[t.Tuple[str, list[list[MatrixData]]]]
as the matrix is always a list
bc_id, study_version, bc_props, bc_props.operator | ||
): | ||
# prepare matrix as a dict to save it in the tree | ||
matrix_url = target.split("/") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems really complicated, why do'nt you just do file_study.tree.save(data=next_matrix, url=matrix_url)
?
Also if it's that simple you could do it inside the generate_replacement_matrices
method to lighten the _apply
code which is already not that simple
…pply_config was overriding all bindings
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mostly minor remarks
# More efficient way of doing things but using less readable commands. | ||
commands = [] | ||
|
||
study_version = StudyVersion.parse(study.version) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't need to parse anymore as study is now a StudyInterface, you can just write study.version
return bcs_output | ||
|
||
def __convert_constraint_input_to_output( | ||
self, bc_json: JSON, bc_input_as_dict: Dict[str, Any], study_version: StudyVersion |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor: JSON = Dict[str, Any] it's a bit weird to use both typings insidethe same method. I would replace JSon by dict[str, Any]
@@ -144,9 +144,9 @@ def test_update_multiple_binding_constraints(self, client: TestClient, user_acce | |||
res = client.get(f"/v1/studies/{study_id}/commands") | |||
assert res.status_code == 200 | |||
json_result = res.json() | |||
assert len(json_result) == 10 | |||
assert len(json_result) == 1 | |||
for cmd in json_result: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor: You can replace the for loop by a check of [0] as you know there's only one element
|
||
|
||
@pytest.fixture | ||
def file_study(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The name of the fixture does not seem to represent what it achieves
@pytest.fixture | ||
def binding_constraint_properties(): | ||
return { | ||
"bc_1": ConstraintInput(group="new_group1", operator="greater", time_step="daily"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems to me the objects here should be Properties instead of ConstraintInput
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The api is called with ConstraintInput which inherit Properties anyway. It's better to test it with ConstraintInput as it's closer to the reality.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's really minor but I disagree, the command expects Properties and not Input
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ConstraintInput is a Properties
return existing_constraint | ||
|
||
study_data.bindings = list(map(update_binding_constraint_dto, study_data.bindings)) | ||
return CommandOutput(status=True, message="_apply_config success !"), {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The message is not really important but here it's not clear.
You can see in other commands what we do usually
group = bc_props_as_dict.get("group") or existing_constraint.group | ||
operator = bc_props_as_dict.get("operator") or existing_constraint.operator | ||
time_step = bc_props_as_dict.get("time_step") or existing_constraint.time_step | ||
areas = bc_props_as_dict.get("areas") or existing_constraint.areas |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Areas and clusters can not be updated as we don't give terms here. You can remove this
) | ||
# It's important to use exclude_unset=True. Otherwise we'd override | ||
# existing values with the default bc_props values. | ||
# Also important to use by_alias=True, so time_step is renamed to type |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's not just for time_step, it's for the filtering also. You can remove this line I think
operator: BindingConstraintOperator, | ||
) -> t.Iterator[t.Tuple[str, t.List[t.List[MatrixData]]]]: | ||
""" | ||
Yield one or two (when operator is "BOTH") matrices intialized with default values. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor: IMO a better wording would be Yield one (or two when operator is "BOTH") ...
Also, typo: initialized
BindingConstraintFrequency.DAILY.value: default_bc_weekly_daily_86, | ||
BindingConstraintFrequency.WEEKLY.value: default_bc_weekly_daily_86, | ||
}[bc_props.time_step].tolist() | ||
yield (target, matrix) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor: My IDE says you can just write yield target, matrix
status=False, | ||
message=f"Path '{target}' does not exist.", | ||
) | ||
except AssertionError as e: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove except asserion error
status=False, | ||
message=f"Path '{target}' does not target a matrix.", | ||
) | ||
except Exception as e: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove this as well
target_matrix = target_matrix[element] | ||
target_matrix[matrix_url[-1]] = next_matrix | ||
file_study.tree.save(replace_matrix_data) | ||
except (KeyError, ChildNotFoundError) as e: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove this
study_file_target = "input/bindingconstraints/bindingconstraints" | ||
url = study_file_target.split("/") | ||
tree_node = file_study.tree.get_node(url) | ||
if not isinstance(tree_node, IniFileNode): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove this file
|
||
excluded_fields = set(ICommand.model_fields) | ||
json_command = self.model_dump(mode="json", exclude=excluded_fields, exclude_unset=True) | ||
# for key in json_command: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove bc_props_by_id key
bc_props_by_id: t.Mapping[str, BindingConstraintProperties] | ||
|
||
def _apply_config(self, study_data: FileStudyTreeConfig) -> t.Tuple[CommandOutput, t.Dict[str, t.Any]]: | ||
# index = next(i for i, bc in enumerate(study_data.bindings) if bc.id == self.id) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use same code that update a single constraint. This is usefull to update objects in cache.
# input_bc_props = create_binding_constraint_config(study_version, **bc_input_as_dict) | ||
|
||
bc_props_by_id = { | ||
key: create_binding_constraint_config(study_version, **value) for key, value in bc_by_id.items() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rename this method
@pytest.fixture | ||
def binding_constraint_properties(): | ||
return { | ||
"bc_1": ConstraintInput(group="new_group1", operator="greater", time_step="daily"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The api is called with ConstraintInput which inherit Properties anyway. It's better to test it with ConstraintInput as it's closer to the reality.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Didn't read everything I assume you took my last comments into account. Only minor remarks / opinions, after that we're good I think
@pytest.fixture | ||
def binding_constraint_properties(): | ||
return { | ||
"bc_1": ConstraintInput(group="new_group1", operator="greater", time_step="daily"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's really minor but I disagree, the command expects Properties and not Input
) | ||
return existing_constraint | ||
|
||
existing_ids = {b.id for b in study_data.bindings} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find this really hard to read but we'll rewrite this when we'll remove the _apply_config and it will be easier to write
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just create a set, to check my elements exists in this set it's a common way to do.
bc_id, study.version, value, output.operator, self._command_context | ||
) | ||
commands.extend(replace_matrix_commands) | ||
# convert ConstraintInput to dict, UpdateBindingConstraints will expect a dict as input |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The command expects Properties not a dict or a ConstraintInput object. IMO either you give the constraintInput as is and you remove the comment or you modify the comment to say you're giving Properties and you really give properties
No description provided.