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

Add validation for number of datetimes in Temporal.interval #17

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

mo-lukecarr
Copy link
Contributor

@mo-lukecarr mo-lukecarr commented Dec 16, 2024

Addresses and resolves the existing TODO comment for validating temporal list lengths.

Currently, the interval field of the Temporal data class doesn't validate the number of datetimes that are provided. Per the specification, each interval should consist of exactly two datetime strings, so we should change the interval from:

interval: List[List[AwareDatetime]]

to:

interval: List[Annotated[List[AwareDatetime], Len(min_length=2, max_length=2)]]

@mo-lukecarr
Copy link
Contributor Author

I appear to get an error in IntelliJ on the use of Len(...) which says Generics should be specified through square brackets. However, tests are still passing locally and a quick search suggests that this is a known issue with JetBrains' inspections (e.g. non-issue).

@PaulVanSchayck
Copy link
Collaborator

Thanks for this draft PR. You probably need to add this snippet to make Python 3.8 happy:

if sys.version_info < (3, 9):
    from typing_extensions import Annotated
else:
    from typing import Annotated

@mo-lukecarr
Copy link
Contributor Author

Thanks for this draft PR. You probably need to add this snippet to make Python 3.8 happy:

if sys.version_info < (3, 9):
    from typing_extensions import Annotated
else:
    from typing import Annotated

My impression (maybe wrong!) is that typing_extensions can be a drop-in for all versions, so I could amend the import to just use from typing_extensions import Annotated for all versions, rather than checking sys.version_info and conditionally importing.

Or is there a specific preference to use this approach? Happy to do so if there is!

@PaulVanSchayck
Copy link
Collaborator

Thanks for this draft PR. You probably need to add this snippet to make Python 3.8 happy:

if sys.version_info < (3, 9):
    from typing_extensions import Annotated
else:
    from typing import Annotated

My impression (maybe wrong!) is that typing_extensions can be a drop-in for all versions, so I could amend the import to just use from typing_extensions import Annotated for all versions, rather than checking sys.version_info and conditionally importing.

Or is there a specific preference to use this approach? Happy to do so if there is!

Ah! Didn't know this :) I guess I don't mind either way too much. Maybe the version check is a bit more explicit, but yours is fine as well.

Could you still add the test for a failing validation case?

@mo-lukecarr
Copy link
Contributor Author

Thanks for this draft PR. You probably need to add this snippet to make Python 3.8 happy:

if sys.version_info < (3, 9):
    from typing_extensions import Annotated
else:
    from typing import Annotated

My impression (maybe wrong!) is that typing_extensions can be a drop-in for all versions, so I could amend the import to just use from typing_extensions import Annotated for all versions, rather than checking sys.version_info and conditionally importing.
Or is there a specific preference to use this approach? Happy to do so if there is!

Ah! Didn't know this :) I guess I don't mind either way too much. Maybe the version check is a bit more explicit, but yours is fine as well.

Could you still add the test for a failing validation case?

Got some time set aside this afternoon to wrap up this PR. I don't want to block the 0.6.0 release much longer!

@mo-lukecarr mo-lukecarr marked this pull request as ready for review January 16, 2025 11:57
Copy link

codecov bot commented Jan 16, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 98.05%. Comparing base (ca0ab14) to head (60bd808).
Report is 4 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main      #17      +/-   ##
==========================================
+ Coverage   98.02%   98.05%   +0.02%     
==========================================
  Files          10       10              
  Lines         203      206       +3     
==========================================
+ Hits          199      202       +3     
  Misses          4        4              
Flag Coverage Δ
unittests 98.05% <100.00%> (+0.02%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@mo-lukecarr
Copy link
Contributor Author

mo-lukecarr commented Jan 16, 2025

@PaulVanSchayck, this PR is ready for review when you have a spare minute.

I've added one bad test case for the validation logic, with a temporal interval that contains a single datetime (rather than a pair). Is this satisfactory for what we'd like to cover in our tests?

The successful tests cases already exist in the repo and cover instances where a pair of datetimes are provided as the interval.


I've actually added another commit that refactors the interval length validation so we can actually unit test how the Pydantic validation logic is configured. Is this overkill, or a suitable approach for testing the logic works as intended?

Copy link
Contributor Author

@mo-lukecarr mo-lukecarr Jan 16, 2025

Choose a reason for hiding this comment

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

I think this testing logic should be in a separate file (rather than test_edr.py) because that file is focused on the different happy/error test cases represented as JSON files.

Thoughts?

Copy link
Collaborator

Choose a reason for hiding this comment

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

In principle I agree, but I don't think these specific tests are needed.

Copy link
Collaborator

@lukas-phaf lukas-phaf left a comment

Choose a reason for hiding this comment

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

Thanks for completing this!

A couple of small comments.

from pydantic import AwareDatetime
from typing_extensions import Annotated
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is typing_extensions an externel dependency or not. It is not clear to me from the docs.

Should we make it explicit we are using this by adding it to dependencies in pyproject.toml, instead of relying on pydantic to install it for us?

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 should just do the same as pydantic does?
https://github.com/pydantic/pydantic/blob/main/pyproject.toml#L46

Copy link
Contributor Author

@mo-lukecarr mo-lukecarr Jan 21, 2025

Choose a reason for hiding this comment

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

I'd suggest that because pydantic installs it for us, and this library is so closely coupled to pydantic, then maybe we don't need to be explicit about it? We'd also be able to just use the version provided by pydantic, rather than possibly bringing in two versions of typing_extensions.

Although, I'm happy to install explicitly as pydantic does, if that's the desired approach.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I prefer to explicitly mention packages that you import, as there is no guarantee that pydantic will keep using/providing it.

I think there can only be one version of a package in a specific Python environment, and that consistency in the requirements is checked during installation. So as long as we use a greater then package requirement, it should always work. Specifically, if we use typing-extensions>=4.12.2, we should be good.

class Temporal(EdrBaseModel):
# TODO: Validate this list has two items (C.7. Temporal Object)
interval: List[List[AwareDatetime]]
interval: List[Annotated[List[AwareDatetime], IntervalLen]]
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why define IntervalLen instead of using the Len(...) in-place?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ah, I see, for testing purposes.

Comment on lines +5 to +6
assert IntervalLen.min_length == 2, "Temporal intervals must have at least 2 items"
assert IntervalLen.max_length == 2, "Temporal intervals must have at most 2 items"
Copy link
Collaborator

Choose a reason for hiding this comment

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

I am probably missing something, but it feels like you are testing that Python can set a class member...

I much prefer the approach with temporal-interval.json. Why not add a test there with 3 elements to ensure that both lower and upper bound are unchanged?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ah, I see now you already mention this in your comments.
As you can see from my comment above, I think this is overkill...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants