Improve Lifetime validation and error handling#249
Conversation
There was a problem hiding this comment.
Pull request overview
This PR refines how microgrid “lifetime” values are represented and consumed across the client-common wrapper layer, aiming to preserve malformed wire data safely while providing explicit failure modes via semantic accessors (aligning with the direction in #239 to move away from fragile “issues as strings” patterns).
Changes:
- Introduce
BaseLifetime,Lifetime,InvalidLifetime, andInvalidLifetimeErrorunderfrequenz.client.common.microgrid, and update lifetime conversion to preserve malformed ranges asInvalidLifetime. - Add safe lifetime accessors (
get_operational_lifetime()) forElectricalComponentandElectricalComponentConnection, and make operational checks raiseInvalidLifetimeErrorwhen malformed data is present. - Update proto converters, tests, and release notes to reflect the new lifetime handling and imports (including moving lifetime conversion out of
frequenz.client.common.types.*).
Reviewed changes
Copilot reviewed 35 out of 35 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/microgrid/proto/v1alpha8/test_lifetime.py | Updates lifetime proto tests to expect InvalidLifetime preservation instead of raising. |
| tests/microgrid/electrical_components/test_simple_components.py | Adjusts component construction expectations (adds model). |
| tests/microgrid/electrical_components/test_problematic.py | Adjusts problematic component tests for new model requirement. |
| tests/microgrid/electrical_components/test_problematic_connection.py | Updates Lifetime import to microgrid module. |
| tests/microgrid/electrical_components/test_power_transformer.py | Updates construction to include model. |
| tests/microgrid/electrical_components/test_inverter.py | Updates construction to include model. |
| tests/microgrid/electrical_components/test_grid_connection_point.py | Updates construction to include model. |
| tests/microgrid/electrical_components/test_ev_charger.py | Updates construction to include model. |
| tests/microgrid/electrical_components/test_electrical_component_connection.py | Adds tests for safe lifetime accessors and invalid-lifetime raising behavior. |
| tests/microgrid/electrical_components/test_electrical_component_base.py | Adds component-level safe lifetime accessor tests and updates model expectations. |
| tests/microgrid/electrical_components/test_battery.py | Updates construction to include model. |
| tests/microgrid/electrical_components/proto/v1alpha8/test_raw_storage.py | Ensures model is preserved in raw/unrecognized component conversion cases. |
| tests/microgrid/electrical_components/proto/v1alpha8/test_electrical_component_connection.py | Updates connection proto conversion expectations: unbounded default and invalid lifetime preservation. |
| tests/microgrid/electrical_components/proto/v1alpha8/test_electrical_component_base.py | Updates base-data proto conversion expectations for lifetime handling and minor issues. |
| tests/microgrid/electrical_components/proto/v1alpha8/test_class.py | Updates test data dict to include model. |
| tests/microgrid/electrical_components/proto/v1alpha8/conftest.py | Moves Lifetime import to microgrid module and sets default lifetime fixture. |
| tests/microgrid/_lifetime/test_lifetime.py | Adjusts lifetime tests to new lifetime type hierarchy (BaseLifetime). |
| tests/microgrid/_lifetime/test_invalid_lifetime.py | Adds tests for InvalidLifetime preserving malformed ranges. |
| tests/microgrid/_lifetime/test_invalid_lifetime_error.py | Adds tests for InvalidLifetimeError behavior and inheritance. |
| tests/microgrid/_lifetime/test_base_lifetime.py | Adds tests ensuring BaseLifetime cannot be instantiated directly. |
| tests/microgrid/_lifetime/conftest.py | Adds shared time fixtures for lifetime-related tests. |
| tests/microgrid/_lifetime/init.py | Introduces package init for lifetime tests. |
| src/frequenz/client/common/types/proto/v1alpha8/_lifetime.py | Removes the old lifetime proto converter from types. |
| src/frequenz/client/common/types/proto/v1alpha8/init.py | Stops exporting lifetime_from_proto from types.proto.v1alpha8. |
| src/frequenz/client/common/types/_lifetime.py | Removes the old types.Lifetime implementation. |
| src/frequenz/client/common/types/init.py | Stops exporting Lifetime from frequenz.client.common.types. |
| src/frequenz/client/common/microgrid/proto/v1alpha8/_lifetime.py | Adds microgrid-scoped lifetime proto conversion that returns `Lifetime |
| src/frequenz/client/common/microgrid/proto/v1alpha8/init.py | Exports lifetime_from_proto from microgrid proto package. |
| src/frequenz/client/common/microgrid/electrical_components/proto/v1alpha8/_electrical_component.py | Updates component proto parsing to use new lifetime types and preserve malformed lifetimes. |
| src/frequenz/client/common/microgrid/electrical_components/proto/v1alpha8/_electrical_component_connection.py | Updates connection proto parsing to use new lifetime types; drops minor-issues collection for lifetime. |
| src/frequenz/client/common/microgrid/electrical_components/_electrical_component.py | Adds get_operational_lifetime() and makes operational checks raise on invalid lifetime. |
| src/frequenz/client/common/microgrid/electrical_components/_electrical_component_connection.py | Adds get_operational_lifetime() and makes operational checks raise on invalid lifetime. |
| src/frequenz/client/common/microgrid/_lifetime.py | Introduces BaseLifetime, Lifetime, InvalidLifetime, InvalidLifetimeError. |
| src/frequenz/client/common/microgrid/init.py | Re-exports new lifetime types at frequenz.client.common.microgrid.*. |
| RELEASE_NOTES.md | Updates release notes entry related to lifetime types/converters. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
032c422 to
d959051
Compare
The `model` is a required field in the protobuf message so it is better to keep the representation consistent. An empty string is preserved as is now. Signed-off-by: Leandro Lucarella <luca-frequenz@llucax.com>
This class will be used as the base for both valid and invalid lifetime classes. Signed-off-by: Leandro Lucarella <luca-frequenz@llucax.com>
`Lifetime`, the existing type, is retroactively made a subclass of `BaseLifetime`. Its field shape, construction API, validation, and operational checks are unchanged. Signed-off-by: Leandro Lucarella <luca-frequenz@llucax.com>
`InvalidLifetime` represents malformed wire data. It carries the same timestamps as `BaseLifetime` without enforcing ordering invariants, so callers can inspect what the server actually sent without using it for operational checks. Signed-off-by: Leandro Lucarella <luca-frequenz@llucax.com>
The dedicated `InvalidLifetimeError`, also a `ValueError` for convenience, carries the offending `InvalidLifetime` on `.lifetime`. Upcoming semantic accessors can therefore reject malformed lifetime data without preventing callers from inspecting the original timestamps. Signed-off-by: Leandro Lucarella <luca-frequenz@llucax.com>
Now the conversion function returns `Lifetime | InvalidLifetime` instead of raising for malformed timestamp ordering. Invalid wire data becomes an `InvalidLifetime` carrying both timestamps, so callers can inspect or report what the server sent. The existing converter retains its current raising behavior for now. Signed-off-by: Leandro Lucarella <luca-frequenz@llucax.com>
Loosen `ElectricalComponent.operational_lifetime` and `BaseElectricalComponentConnection.operational_lifetime` to `Lifetime | InvalidLifetime`, preserving malformed timestamp ordering received from protobuf instead of replacing it with an unbounded `Lifetime`. Add `get_operational_lifetime() -> Lifetime` to both class hierarchies so callers that require a valid lifetime receive either the valid value or an `InvalidLifetimeError` carrying the malformed one. The `is_operational_at()` and `is_operational_now()` shortcuts now use the safe accessor and raise the same error for malformed data. Missing and present-but-empty protobuf lifetimes continue to become `Lifetime()`, as both represent an unbounded lifetime in the upstream schema. Signed-off-by: Leandro Lucarella <luca-frequenz@llucax.com>
Now that invalid lifetimes are encoded into the type system, we don't need to report issues with them via the major/minor issues side-channel. Signed-off-by: Leandro Lucarella <luca-frequenz@llucax.com>
`src/.../types/_lifetime.py` now exposes four public symbols, so the test
file grew too large. Split it following the repository convention:
tests/types/test_lifetime.py
-> tests/types/_lifetime/test_<thing>.py
Test bodies and assertions are unchanged. Shared time fixtures move to the
subdirectory's `conftest.py`, and test names drop redundant type prefixes.
Signed-off-by: Leandro Lucarella <luca-frequenz@llucax.com>
This is to match where `Lifetime` is defined in the protobuf messages. Signed-off-by: Leandro Lucarella <luca-frequenz@llucax.com>
Adopt the `<invalid:VALUE>` marker convention. `Lifetime` renders as a half-open interval `(start,end]`; `None` endpoints become `-inf`/`+inf`. `InvalidLifetime` wraps the same rendering in an explicit `<invalid:...>` marker to flag the relational invariant (`start <= end`) violation. Signed-off-by: Leandro Lucarella <luca-frequenz@llucax.com>
Signed-off-by: Leandro Lucarella <luca-frequenz@llucax.com>
|
Maybe you can give this a look too @cwasicki ? 😇 |
| @pytest.fixture | ||
| def present() -> datetime: | ||
| """Provide the current UTC time.""" | ||
| return datetime.now(timezone.utc) |
There was a problem hiding this comment.
Is now mocked or do tests depend on time?
There was a problem hiding this comment.
They are not mocked nor they depend on time, sort of. It is not mocked but we create past and future relative to present, so it works, but we should mock it, yeah, that was a dumb idea. But not for this PR. :)
We now also allow storing invalid values, but encoded via a new type
InvalidLifetime. This way users still can't read invalid values accidentally, but still allows users to inspect invalid values if they need to, instead of only reporting via logging.Add new safe accesors for
ElectricalComponentandElectricalComponentConnectionlifetimeattributes.Part of #239 and #251.