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
[Feature Request] Plugin API, Phase III: The __beartype_hint__()
protocol
#192
Comments
YES. YESSSSS. Ok, so once again, I have to digest this, but i'm pretty sure it completely nukes the usefulness of my efforts last night. STILL it would help me a great deal if you @leycec and perhaps @antonagestam would see if I was way off base with this proof-of-concept to realize the error of my protocol ways and embrace the way phantom-types hack-and-slashed the path through the jungle before me? Meaning, I have a PR that is meant only to show a diff on how I think a beartype-backed phantom-type would operate... and it would help me a lot if I could understand what about that thing would change if this marvelous API was introduced. see here pretty please?? 😄 |
I'm probably gonna spam stuff, as I think of it, but first... Say we have two Then what happens if I don't explicitly use validators in an inheriting class, but it's implicit in the other classes' E.g. usage: @beartype
class PositiveInt(int, Is[lambda x: x>0]):
...
@beartype
class PrimeInt(int, Is[sieve_eratosthenes]):
...
@beartype
class EvenInt(int, Is[lambda x: x%2==0]):
... Then what happens with: @beartype # product type:
class PositivePrime(PositiveInt, PrimeInt):
...
@beartype # sum type:
class PrimeOrEven(PrimeInt | EvenInt):
...
|
OH another thought:
|
@leycec another thing I'm encountering (how do I always manage to break stuff the very first time I try to use said thing??) in my particular flavor of "the wild": Annotated GenericsYeah, so... I want a pluggable system for folks to add parameters to a "type" and have import static_frame as sf
import beartype.typing as bt
from beartype.vale import IsAttr, IsEqual
from beartype.door import is_bearable
# first we need to make typevars for generics
EntityKind = bt.TypeVar('EntityKind', bound=str)
FlagArray = bt.Annotated[
sf.SeriesHE, # it's a series
IsAttr['name', IsEqual[bt.Tuple[EntityKind, EntityKind]]] # it's got a (name1,name2) name
## ^ this part should propagate "hey this thing is generic!"
& IsAttr['index', IsAttr['depth', IsEqual[2]]] # <- this means it's hierarchical
]
"""doesn't work either:
FlagArray = bt.Annotated[
object,
IsInstance[sf.SeriesHE]
& IsAttr['name', IsEqual[bt.Tuple[EntityKind, EntityKind]]]
& IsAttr['index', IsAttr['depth', IsEqual[2]]]
]
""" trying to subscript TypeError: typing.Annotated[object, IsInstance[static_frame.core.series.SeriesHE] & IsAttr['name', IsEqual[tuple[~EntityKind, ~EntityKind]]] & IsAttr['index', IsAttr['depth', IsEqual[2]]]] is not a generic class Same for SO... how to make parameterized Validators for reuse? And... can I use the type parameterization system with this new proposal? e.g., using the new PEP 695 syntax, since I'm tired of typing @beartype
@dataclass
def DoorsOfPerception[T<:str](IsAttr['niceplace_or_hell',
(IsEqual[T] & IsAttr['circle_of_hell', IsEqual[None]]) |
(IsEqual['hell'] & IsAttr['circle_of_hell', ~IsEqual[None]])
]):
niceplace_or_hell: T | Literal['hell']
circle_of_hell: Optional[Literal[
'limbo', 'lust', 'gluttony', 'greed', 'wrath',
'heresy', 'violence', 'fraud', 'treachery',
]] So I want to get something like, yknow,
The issue here is, I'm not even really sure how you're supposed to use generic parameters at runtime, anyway. All of your EDIT: looks like PEP675 people beat me to it, but this begs the questions 1) why we don't also get Footnotes
|
Gah! So sorry for the radio silence. I've been delving deep into the stinky bowels of issue #180 for our upcoming You ask the pertinent questions that hit me hard in the gut. Like the entirety of PEP 695 -- "Type Parameter Syntax", which is both amazing and horrifying. Thank you so much for giving me the heads up on that, although my head is now throbbing with thunderous pain. Seriously. @beartype must support PEP 695, because everyone will immediately start using that in Python 3.12, but I don't even know if runtime type-checkers can reasonably support the entirety of PEP 695. Apparently, we're now throwing out explicit type variance (e.g., This is QA madness. Wait. What were we chatbotting about again? 😮💨 Plugins! Right. In theory, As for everything intriguing that you just asked... I swear I read everything but currently have no idea. The cat is snoring fitfully and I am pounding the keyboard uselessly. You are correct about everything, I suspect. The entire # Seriously? We're seriously using comparison operators in
# implicit type parameter syntax? But... what does that even mean?
def DoorsOfPerception[T<:str](...): ... Oh. I shudder as I see. Did you unearth that syntactic monstrosity from the "Rejected Ideas" section of PEP 695? Specifically, this?
Darn you, PEP 695 authors! Darn you to all heck! |
This commit is the first in a commit chain defining the PEP 544-compliant `beartype.plug.BeartypeHintable` protocol, en-route to resolving feature request #192 technically submitted by myself but spiritually submitted by super-nice NIST scientist @tbsexton (Thurston Sexton) in his unquenchable search for a utopian DSL for typing Python that may never exist – *but should*. Specifically, this commit: * Defines a new public `beartype.plug` subpackage. * Defines a new public `BeartypeHintable` protocol in that subpackage, Naturally, everything is mostly undocumented, very untested, and absolutely non-working. (*Conflicted conspiracy of lickable flicks!*)
So. It begins with a whimper. @tbsexton: Sincere apologies on failing to deeply respond to your incredibly enlivening exegesis on Pythonic type hint plugins here and elsewhere. Gaah! I can only hope that you and yours had a fantastic Christmas and New Year's – and do solemnly swear on my cat's grossly distended belly to reply to everything you wrote before resolving this feature request. Let's do this, Team Bear That Types! 🐻 ⌨️ |
This commit is the next in a commit chain defining the PEP 544-compliant `beartype.plug.BeartypeHintable` protocol, en-route to resolving feature request #192 technically submitted by myself but spiritually submitted by super-nice NIST scientist @tbsexton (Thurston Sexton) in his unquenchable search for a utopian DSL for typing Python that may never exist – *but should*. Specifically, this commit documents, unit tests, and finalizes the implementation of our recently added `beartype.plug.BeartypeHintable` protocol. (*Ultimate ulna, mate!*)
This commit is the next in a commit chain defining the PEP 544-compliant `beartype.plug.BeartypeHintable` protocol, en-route to resolving feature request #192 technically submitted by myself but spiritually submitted by super-nice NIST scientist @tbsexton (Thurston Sexton) in his unquenchable search for a utopian DSL for typing Python that may never exist – *but should*. Specifically, this commit: * Drafted initial (read: untested) usage of the `__beartype_hint__()` dunder class method in our core `beartype._check.conv.convreduce.reduce_hint()` function. * Began drafting unit tests exercising that usage. * Repairs unit tests broken by prior commits in this commit chain. Thankfully, they only pertained to Python 3.7 -- which nobody should care about in 2023. This means you! * Unrelatedly, *very* lightly documented the read-only `beartype.door.TypeHint.{args,hint}` properties. (*Beady eyes on a heady I!*)
This commit is the next in a commit chain defining the PEP 544-compliant `beartype.plug.BeartypeHintable` protocol, en-route to resolving feature request #192 technically submitted by myself but spiritually submitted by super-nice NIST scientist @tbsexton (Thurston Sexton) in his unquenchable search for a utopian DSL for typing Python that may never exist – *but should*. Specifically, this commit continues iterating on the implementation, usage, and testing of this protocol. Sadly, we suddenly realized in a fit of mad panic that this protocol should *not* actually be a protocol. Why? Because doing so coerces *all* user-defined classes subclassing this protocol into typing generics -- which is absolutely *not* intended. Consequently, the next commit in this commit chain will: * Relax `BeartypeHintable` into a standard abstract base class (ABC). * Create a new private `_BeartypeHintableProtocol` for internal usage, mostly in detecting classes implicitly satisfying this ABC. (*Thunderous terror or tons of nuns on tundra?*)
This commit is the next in a commit chain defining the PEP 544-compliant `beartype.plug.BeartypeHintable` protocol, en-route to resolving feature request #192 technically submitted by myself but spiritually submitted by super-nice NIST scientist @tbsexton (Thurston Sexton) in his unquenchable search for a utopian DSL for typing Python that may never exist – *but should*. Specifically, this commit relaxes `BeartypeHintable` into a standard abstract mixin *and* substantially revises the docstring for that mixin to reflect its new nature. (*Ultrasonic Ultraman or Sonic?*)
This commit is the next in a commit chain defining the PEP 544-compliant `beartype.plug.BeartypeHintable` protocol, en-route to resolving feature request #192 technically submitted by myself but spiritually submitted by super-nice NIST scientist @tbsexton (Thurston Sexton) in his unquenchable search for a utopian DSL for typing Python that may never exist – *but should*. Specifically, this commit adds support reducing type hints via user-defined `__beartype_hint__()` dunder methods to our existing reduction machinery. Unfortunately, doing so induces infinite recursion (...that's bad) in the common case of a `__beartype_hint__()` implementation returning a type hint of the form `Annotated[cls, ...]`. Since resolving that would require non-trivial refactoring of our current breadth-first code generation algorithm into a depth-first code generation algorithm, we're temporarily stymied in this commit chain. Fortunately, we will be performing this refactoring shortly -- because we need to anyway, for various and sundry reasons. Until then, this commit chain is sadly on hold. (*Formidable middling form is gormless meddling!*)
This commit is the next in a commit chain defining the PEP 544-compliant `beartype.plug.BeartypeHintable` protocol, en-route to resolving feature request #192 technically submitted by myself but spiritually submitted by super-nice NIST scientist @tbsexton (Thurston Sexton) in his unquenchable search for a utopian DSL for typing Python that may never exist – *but should*. Specifically, this commit documents additional work in this commit chain needed to detect and squelch unwanted recursion in our existing support for reducing type hints via user-defined `__beartype_hint__()` dunder methods. (*Floppy lop-sided hides!*)
This commit is the next in a commit chain defining the PEP 544-compliant `beartype.plug.BeartypeHintable` protocol, en-route to resolving feature request #192 technically submitted by myself but spiritually submitted by super-nice NIST scientist @tbsexton (Thurston Sexton) in his unquenchable search for a utopian DSL for typing Python that may never exist – *but should*. Specifically, this commit documents additional work in this commit chain needed to detect and squelch unwanted recursion in our existing support for reducing type hints via user-defined `__beartype_hint__()` dunder methods. While we *could* perform this work in the `beartype 0.12.0` release cycle, doing so seems increasingly unproductive. Let's just ship this monster already! (*Mewling pews strewn with straw news!*)
Also, I think a better way for me to ask all that junk up there is:
See, for instance, the way Plum enables Parametric Types. Since this isn't something strictly related to multiple dispatch, this plugin api might let @wesselb keep the parametric types as a separate micro-repository beartype plugin, or let users get them for free via The other place I've seen parametric types (recently) is Pandera's dataframe validators, since the categoricalDType constructors take user parameters (the valid categories, orders, etc). I've been using Beartype validators to design these rather than heavier pandera schemas, but to allow better dispatching I'd love to know at runtime, say, whether the (N.B. Also I realized where I got that |
This feature request is the third milestone on the great journey to a first-class best-of-breed plugin architecture for @beartype. This milestone is orthogonal to the second milestone (i.e.,
beartype.sign
); that is, these two plugin milestones may be implemented in any order. Indeed, despite offering more powerful plugin machinery than the second milestone, this milestone is arguably simpler to implement and document. In other words, we might want to do this one first. universe: 0. @beartype: 1.In this phase, we introduce a new public
__beartype_hint__()
protocol enabling users to attach type hint transforms to arbitrary user-defined classes. Since type hint transforms include Turing-complete beartype validators (e.g.,typing.Annotated[muh_class, beartype.vale.Is[muh_instance: bool(muh_instance)]]
), this protocol effectively enables users to define arbitrary runtime type-checking. Plugin architecture, I summon thee! 🧙♂️By default, @beartype type-checks any passed parameter or return value against a user-defined type
cls
with a trivialisinstance(obj, cls)
test at runtime. Now, however, you'll be able to enhance how your classes are type-checked with additional constraint checks dynamically defined by you at runtime and (more importantly) permanently attached to those classes and all instances of those classes.Dig Deep Like Shovel Knight on a Digging Bender
Let's dig deep into what exactly we're talking about here.
Consider the following PEP 544-compliant protocol:
@beartype deeply type-checks
typing.Literal[...]
type hints and thus validates theheaven_or_hell
field as expected. But what about thecircle_of_hell
type hint? Clearly, Heaven has no Circles of Hell. Clearly. We thus want @beartype to validate that:heaven_or_hell == 'heaven'
, thencircle_of_hell is None
.heaven_or_hell == 'hell'
, thencircle_of_hell in ('limbo', 'lust', 'gluttony', 'greed', 'wrath', 'heresy', 'violence', 'fraud', 'treachery')
.In other words, we want @beartype to type-check conditionally depending upon the contents of the
DoorsOfPerception
instance being type-checked. How can we do this depraved act of fiendish difficulty? Simple.Digging a Hole So Deep We Ended up on the Moon
We use the
__beartype_hint__()
protocol to instruct @beartype to apply additional constraints when runtime type-checking.First, we easily define this protocol using the existing
typing.Protocol
machinery standardized by PEP 544:Second, we augment our previously defined
DoorsOfPerception
dataclass with additional runtime type-checking defined via this protocol:Done and done. @beartype now detects that
DoorsOfPerception
satisfies theBeartypeHintable
protocol and implicitly applies the additional conditional constraints defined by the__beartype_hint__()
dunder method when type-checking instances of that dataclass at runtime.Shorthand for Shorties
Okay. Everything is great, but it's also a bit long-winded. Can we improvise some sort of convenient syntactic sugar that elides away all of the boilerplate implied by actual real-world usage of the
BeartypeHintable
protocol?Yes. Yes, we can. Consider this terse shorthand for implementing the exact same dataclass as implemented above – but with far fewer lines of DRY-violating scaffolding:
...wut u say!?!?
So. The idea here is that beartype validators (i.e., public
beartype.vale.Is*[...]
constraint factories) can be generalized to be subclassable. When subclassed, beartype validators will automatically:__mro__
) of the class being subclassed with thebeartype.BeartypeHintable
protocol.__beartype_hint__()
dunder classmethod to that class.You are possibly now muttering to yourself in the candle-lit darkness of your own peculiar (wo)mancave: "B-b-but... that's impossible!" Actually, it's all too possible; I used to hate these sorts of maleficent shenanigans, because the
typing
module does exactly this sort of thing all over the place. PEP 484 refers to this decadent behaviour as "type erasure."In fact, this sleight-of-hand was standardized over five years ago by PEP 560 via the mostly undocumented
__mro_entries__()
dunder method. By abusing__mro_entries__()
, any type (including the type of beartype validators) can transparently replace itself on-the-fly with any other arbitrary type(s) in the MRO of subclasses.In this case, the type of beartype validators will define a trivial
__mro_entries__()
that just replaces itself withBeartypeHintable
when subclassed. Darkness: my old friend.\o/
This syntactic sugar is strongly inspired by @antonagestam's wondrous
phantom-types
, which applies a similar syntax to similar great effect (albeit implemented in a completely different low-level way with__instancecheck__()
dunder metaclass methods, which itself is endlessly fascinating). Thanks so much, @antonagestam!That's Just How We Roll in 2023
In theory, the
BeartypeHintable
protocol should suffice to provide a fully-complete plugin architecture. The__beartype_hint__()
dunder method can be implemented to return Turing-complete beartype validators capable of doing literally anything including bad stuff but let's ignore that (i.e.,typing.Annotated[{user_type}, beartype.vale.Is[{plugin_func}]]
).Users: commence rejoicing once @leycec actually does something about this.
The text was updated successfully, but these errors were encountered: