Skip to content
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

Refactor dates handling #121

Merged
merged 16 commits into from
Feb 13, 2025
Merged

Refactor dates handling #121

merged 16 commits into from
Feb 13, 2025

Conversation

leclairm
Copy link
Contributor

@leclairm leclairm commented Feb 6, 2025

Fix #119, #97 and incidentally #108

Short summary: essentially remove as many None as possible from date related attributes in order to make the code more understandable.

Methods

  • Insert dedicated classes to test types instead of testing for None
  • Move some logic to the yaml models although it was already argued against once
  • Introduce cycling and target_cycle keywords in the config format

Parts to treat

  • Previous _WhenBasemodel
  • ConfigCycle

@leclairm leclairm changed the title None dates refactor Refactor dates handling Feb 6, 2025
@leclairm leclairm linked an issue Feb 6, 2025 that may be closed by this pull request
@leclairm leclairm marked this pull request as ready for review February 10, 2025 21:54
@leclairm leclairm requested review from DropD and agoscinski February 10, 2025 21:58
@leclairm leclairm force-pushed the none-dates-refactor branch from bc2a275 to 561a5a8 Compare February 11, 2025 20:22
Copy link
Collaborator

@DropD DropD left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the design, there are some things to still address though.

Comment on lines 496 to 505
@field_validator("type")
@classmethod
def is_file_or_dir(cls, value: str) -> str:
"""."""
valid_types = ("file", "dir")
if value not in valid_types:
msg = f"Must be one of {valid_types}"
raise ValueError(msg)
return value

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This validator is no longer useful and was removed in #120 .

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this went unnoticed during rebasing because I changed the name of the file and #120 got merged after.

msg = f"For cycle {self.name!r} the period {self.period!r} is negative or zero."
raise ValueError(msg)
return self
cycling: Annotated[Cycling, BeforeValidator(select_cycling)] = OneOff()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a great way of selecting which subclass to parse to 👍.

Comment on lines 95 to 100
... cycle_point=DateCyclePoint(
... start_date=datetime(1000, 1, 1),
... stop_date=datetime(1000, 1, 2),
... begin_date=datetime(1000, 1, 1),
... end_date=datetime(1000, 1, 2),
... ),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It just occurred to me while reading this that I didn't see an explanation anywhere for the meaning of start_date / stop_date vs begin_date / end_date. I assume one of these pairs refers to the overall experiment and one to the cycle?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, exactly. It's missing some docstring. I followed the ICON convention for overall start/stop and then chose the other available pair, begin/end, for the current cycle. Currently, we set ICON start and stop at each cycle so I needed this info to be carried along. And it's very likely that other tasks will want to access begin/end dates somehow (#61 and #85)

@leclairm leclairm requested a review from DropD February 12, 2025 22:03
Copy link
Collaborator

@DropD DropD left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good now

Copy link
Collaborator

@GeigerJ2 GeigerJ2 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work, @leclairm, thanks a lot! Both, code and YAML files now read much cleaner. LGTM (looks good to me let's get this merged)!

from __future__ import annotations

from abc import ABC, abstractmethod
from collections.abc import Iterator # noqa: TCH003 needed for pydantic
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rules should be TC002 and TC003, no?
https://docs.astral.sh/ruff/rules/typing-only-third-party-import/
Possibly, we could also add them here if they are relevant in more places as we use pydantic throughout:

Sirocco/pyproject.toml

Lines 71 to 75 in bb303d0

[tool.ruff.lint]
ignore = [
"TID252", # prefer relative import over absolute
"TRY003", # write custom error messages for formatting
]

But not sure yet 🤔

case Cycling():
return spec
case dict():
if spec.keys() != {"start_date", "stop_date", "period"}:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if spec.keys() != {"start_date", "stop_date", "period"}:
if set(spec.keys()) != {"start_date", "stop_date", "period"}:

Maybe explicitly convert to a set here, so we compare the same objects, and not set and dict_keys?

return spec
case dict():
if not all(k in ("at", "before", "after") for k in spec):
msg = "when keys can only be 'at', 'before' or 'after'"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
msg = "when keys can only be 'at', 'before' or 'after'"
msg = "'when' keys can only be 'at', 'before' or 'after'"

case TargetCycle():
return spec
case dict():
if tuple(spec.keys()) not in (("date",), ("lag",)):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if tuple(spec.keys()) not in (("date",), ("lag",)):
if not (("date" in spec) ^ ("lag" in spec)):

Using XOR? I suppose there can be no other keys?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's actually making sure there are no other keys.

Comment on lines +157 to +159
target_cycle: Annotated[TargetCycle, BeforeValidator(select_target_cycle)] = NoTargetCycle()
when: Annotated[When, BeforeValidator(select_when)] = AnyWhen()
parameters: Annotated[dict[str, Literal["all", "single"]], BeforeValidator(check_parameters_spec)] = {}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice to read :3

@@ -613,6 +566,20 @@ def get_plugin_from_named_base_model(
]


def check_parameters_lists(data: Any) -> dict[str, list]:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can move all the pydantic validators that are now defined as standalone functions into a dedicated file validators.py? Kind of separation of concerns (ofc I know, though, that the validators are connected to the Data models). Just proposing this bc I found it a bit difficult to go through this file, switching between BaseModel derived classes that define our data models, and functions that do some actions.

Copy link
Collaborator

@DropD DropD Feb 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like that to happen, but in a different PR to avoid conflicts right now.

Copy link
Contributor Author

@leclairm leclairm Feb 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I had the same thought while implementing. I even started by putting some validators in _utlis.py but didn't want to go all the way. Actually _utils.py is only validators.

@DropD DropD merged commit d54ad9c into main Feb 13, 2025
7 checks passed
@DropD DropD deleted the none-dates-refactor branch February 13, 2025 13:51
@DropD DropD restored the none-dates-refactor branch February 13, 2025 13:54
DropD added a commit that referenced this pull request Feb 13, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants