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 GroupedMetadata as a base class for Interval #12

Merged
merged 10 commits into from Jul 25, 2022

Conversation

adriangb
Copy link
Contributor

The main idea here is to generalize the pattern in Interval so that Pydantic and similar can define their Field as inheriting from GroupedMetadata, thus anything that can parse annotated-types will know how to unpack it (if it is not already unpacked by the PEP-646) even if it is a custom subclass in a library like Pydantic.

Without this, some generic annotated-types parser would not know how to handle Pydantic's Field type without specific knowledge of Pydantic.

annotated_types/__init__.py Outdated Show resolved Hide resolved
@codecov-commenter
Copy link

codecov-commenter commented Jul 24, 2022

Codecov Report

Merging #12 (315d175) into main (12855f7) will increase coverage by 0.11%.
The diff coverage is 100.00%.

@@            Coverage Diff             @@
##             main      #12      +/-   ##
==========================================
+ Coverage   95.77%   95.89%   +0.11%     
==========================================
  Files           2        2              
  Lines         142      146       +4     
  Branches       35       36       +1     
==========================================
+ Hits          136      140       +4     
  Misses          6        6              
Impacted Files Coverage Δ
annotated_types/__init__.py 93.25% <100.00%> (+0.31%) ⬆️

📣 Codecov can now indicate which changes are the most critical in Pull Requests. Learn more

@Zac-HD
Copy link
Member

Zac-HD commented Jul 25, 2022

Oh, very nice! I think that all this needs is documentation in the README as well as the lovely docstring. A few points to clarify:

  • I'd be inclined to make iter an abstractmethod, so that downstream implementors actually do implement it.
  • We should also specify that iter should (to the extent possible) fully describe the constraints this metadata poses in terms of standard annotated-types constraints, and may include other annotations.
  • And then also specify that consumers of the annotated-types standard should check for GroupedMetadata instead of Interval etc.

After that, let's merge and release a new version!

@adriangb
Copy link
Contributor Author

adriangb commented Jul 25, 2022

  • I'd be inclined to make __iter__ an abstractmethod, so that downstream implementors actually do implement it.

Sounds good to me. Can just add an @abstractmethod without inheriting from abc.ABC and have type checkers enforce it, or do we need to add the metaclass? I forget.

  • We should also specify that __iter__ should (to the extent possible) fully describe the constraints this metadata poses in terms of standard annotated-types constraints, and may include other annotations.
  • And then also specify that consumers of the annotated-types standard should check for GroupedMetadata instead of Interval etc.

I think what you are saying is that GroupedMetadata should not have any meaning on its own, all of its information should be contained in the BaseMetadata's that get unpacked by __iter__(), right?

What do you think of having GroupedMetadata not inherit from BaseMetadata? I think it makes it clearer that GroupedMetadata in and of itself has no meaning.

@adriangb
Copy link
Contributor Author

@Zac-HD I'm terribly sorry I hit edit instead of quote on your comment, so I edited yours instead of posting my own. I restored it now

@Zac-HD
Copy link
Member

Zac-HD commented Jul 25, 2022

Sounds good to me. Can just add an @abstractmethod without inheriting from abc.ABC and have type checkers enforce it, or do we need to add the metaclass? I forget.

Have to add the metaclass, which is most easily done by inheriting from abc.ABC.

I think what you are saying is that GroupedMetadata should not have any meaning on its own, all of its information should be contained in the BaseMetadata's that get unpacked by __iter__(), right?

Yep.

What do you think of having GroupedMetadata not inherit from BaseMetadata? I think it makes it clearer that GroupedMetadata in and of itself has no meaning.

That sounds good to me!

That said, it seems useful to have a single checkable class. Maybe BaseMetadata should inherit from GroupedMetadata, and define __iter__ as yield self?

@adriangb
Copy link
Contributor Author

Maybe BaseMetadata should inherit from GroupedMetadata, and define __iter__ as yield self?

That sounds nice in theory, but I'm weary of creating class hierarchies if I don't really need them. It's really no more LOC or complexity for consumers to have two checks nested or at the same level (you need 2 anyway), I'll push that version in a few min

@adriangb
Copy link
Contributor Author

Pushed. Like I said the difference is minor, there are always going to be 2 isinstance checks, it's just about what level they are at:

https://github.com/annotated-types/annotated-types/pull/12/files#diff-41d180f6f7233d175c2dfe36c45c4ea80eed4754fa7ed4eed46ccde1b2a778c7

Copy link
Member

@Zac-HD Zac-HD left a comment

Choose a reason for hiding this comment

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

Implementation LGTM, pending documentation.

tests/test_main.py Outdated Show resolved Hide resolved
Co-authored-by: Zac Hatfield-Dodds <zac.hatfield.dodds@gmail.com>
@adriangb
Copy link
Contributor Author

adriangb commented Jul 25, 2022

I started writing the docs and it felt a bit awkward because we don't really have any documentation discussing BaseMetadata or how one is supposed to "consume" any of the metadata in the first place. I think this warrants it's own section of the docs and the current section should be moved into a subheading Constraints or something like that. Does this sound reasonable, and would a followup PR be appropriate for this?

@Zac-HD
Copy link
Member

Zac-HD commented Jul 25, 2022

Sounds reasonable; could be a follow-up or part of this PR; I'm happy with either. If follow-up, I think this is ready to merge (though not release without docs).

@adriangb adriangb merged commit f59cf6d into annotated-types:main Jul 25, 2022
@samuelcolvin
Copy link
Contributor

LGTM, my only concern is about use of abc, python/cpython#92810 - I basically live in fear of abs these days.

But I guess in this situation it:

  1. Should be okay as the mro shouldn't get too deep
  2. shouldn't be on a critical path
  3. Is a perfect use for abc which would be much less clear without

@adriangb
Copy link
Contributor Author

Sorry I merged right before your comment.

Agreed on avoiding ABCs, but I think we couldn't do much here. Protocol would be a no-go because Protocol + isinstance isn't great either.

We could always remove it and just manually raise a NotImplementedError or something.

@samuelcolvin
Copy link
Contributor

We could always remove it and just manually raise a NotImplementedError or something.

That was going to be my suggestion, then the page updated as the PR was merged :-).

I think this is fine for now, surely abcs will have to be fixed in cpython soon + my above comments.

@adriangb
Copy link
Contributor Author

If you feel strongly, we can still change it!

@Zac-HD
Copy link
Member

Zac-HD commented Jul 25, 2022

(looks like re snuck back in somehow)

Happy to do whatever on abc, so long as the error is raised when subclassing, not when you try to iterate.

@samuelcolvin
Copy link
Contributor

Happy to do whatever on abc, so long as the error is raised when subclassing, not when you try to iterate.

If we can do that, that's great with me. But is there a reliable (and fairly simple!) way to raise an error when defining subclass if a method hasn't been implemented?

If not, let's leave it as-is.

@adriangb
Copy link
Contributor Author

You can implement __init_subclass__ to hook into subclassing, but this won't do anything for linters/static analysis.

@Zac-HD
Copy link
Member

Zac-HD commented Jul 25, 2022

I'm not too worried about linters if we can get a fully-reliable runtime check, and it looks like __init_subclass__ would do that with less weird-abc-related performance concerns 👍

@adriangb
Copy link
Contributor Author

I opened #16, we can continue that discussion there

@Zac-HD
Copy link
Member

Zac-HD commented Jul 25, 2022

I like it enough to merge already 😆

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.

None yet

4 participants