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

fix(deps): update dependency beartype to >=0.20.0,<0.21.0 #21

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

renovate[bot]
Copy link

@renovate renovate bot commented Mar 5, 2025

This PR contains the following updates:

Package Change Age Adoption Passing Confidence
beartype (changelog) >=0.19.0,<0.20.0 -> >=0.20.0,<0.21.0 age adoption passing confidence

Release Notes

beartype/beartype (beartype)

v0.20.0: Beartype 0.20.0: The Balding Bears Are Back in QA Town

Compare Source

Beartype 0.20.0 catapults out of your CRT monitor from the 90's that contains only four pounds of lead, across your mechanical keyboard with the clacky keys and vibrant crumb trails, and into your lap. Startled, you shriek in fear. Then astonishment. Then back to fear. Trembling, your fingers reach for...

pip install --upgrade beartype     # <-- bugger the bugs

Caveat Emptor: What follows is a lazy copy-paste of our prior changelog for Beartype 0.20.0 Release Candidate 0. Therefore, this is already boring the snot out of you. That's a good thing. Laziness is the root of all QA stability.

@​beartype 0.20.0 gurgles contentedly as you wipe the birthing fluids from its forehead. You choke back tears. In a moment of madness that borders on the divine, you pull the trigger. You update. You're in too deep now. The sunk cost fallacy you feel is real. Your devbox shudders. Your mechanical keyboard spills sugary soda all over itself. Your RSI-wracked mouse hand pulsates with the pre-install jitters. Surely the second coming of QA is at hand.

@​beartype 0.20.0: and you thought Gymnastics Turtle was weird

@​beartype 0.20.0 is brought to you by...

GitHub Sponsors: When You Befriend the Bear, You've got a Bear for Life

This release comes courtesy these proud GitHub Sponsors, without whom @​leycec's cats would currently be eating grasshoppers:

Thanks so much, masters of fintech and metrology.

The Masters of Fintech and Metrology. That's who.

And now for something fishy.

Did You Do Anything Except Break Everything?

Absolutely! Well, surely. Well, probably. Well, possibly. Well, maybe.

@​beartype 0.20.0 brings to bear a whole new lineup of QA goodies. You never knew you needed these QA goodies until a small voice inside of you whispered:

"You do."

Before we get to some actual meaningful content in this changelog, though...

Blast Hardcheese isn't to blame for @​beartype 0.20.0's litany of questionable new "features"... but your coworker might be

Tell @​leycec What to Do in This Thrilling GitHub Poll 😮

Left to my own devices, I just play video games and obsessively rummage around in the bike shed with PEP standards nobody cares about. This is why...

I implore you to vote in this GitHub forum poll. Yes. It's all true. User opinions always mattered, but now user opinions really matter. I can't pretend they don't exist anymore. For the first time ever, you can force me to do your questionable and unsavoury bidding. Tell me what to do this summer. Vote now. Vote often. Vote hard. But for the sake of the bugs crushing your workload, vote. 🌝

a vote for bob evil is a vote for @​beartype 0.21.0

Iterable[...] + Container[...] = BBFFLs (Best Bear Friends for Life)

@​beartype now deeply type-checks the last remaining PEP 484- and 585-compliant container type hints in O(1) constant time, including:

  • collections.abc.Container[...] type hints.
  • collections.abc.Iterable[...] type hints.
  • collections.abc.Reversible[...] type hints.
  • typing.Container[...] type hints.
  • typing.Iterable[...] type hints.
  • typing.Reversible[...] type hints.

@​beartype now type-checks exactly one item of containers annotated with general-purpose container type hints like Iterable[...] and Container[...] in O(1) constant time. How? Smarty bear pants. @​beartype intelligently detects whether a container is:

  • A sequence (e.g., list[str]), in which case a pseudorandom item of that
    sequence is type-checked.
  • A collection (e.g., set[int]), in which case only the first item of that
    collection is type-checked.
  • Neither, in which case @​beartype avoids deeply type-checking anything. Nothing good is better than something bad.


Plum users may now dispatch on all of the above. collections.abc.Iterable[...] is the Big One™, of course. This is the profit of depending on @​beartype. You wait years for @​leycec to do something. Finally, @​leycec does a thing. Your code works. Users cheer. It is delicious.

beartype 0.20.0 isn't laughing. beartype 0.20.0 doesn't even know what's happening anymore...

Type Variables: More Typing, More Variables

@​beartype now deeply type-checks PEP 484-compliant type variables (e.g., T = typing.TypeVar('T')) for a variety of common use cases. Mostly, this means type variables whose type-checking can be entirely decided at decoration time by the @beartype decorator.

Since deciding type-checking at decoration time is really fast, deeply type-checking type variables in these use cases is really fast as well. Like, O(1) fast. Like everything @​beartype does, these type variables are cost-free. They don't cost anything, so you'd might as well use them. This probably marks the first serious attempt by any package to tackle type variables at runtime.

This support fully covers these common use cases:

  • PEP 484- and 585-compliant subscripted generics (e.g., the type hint MuhList[int] given the type class MuhList[T](list[T]): ...).
  • PEP 695-compliant subscripted type aliases (e.g., the type hint MuhAlias[int] given the type alias type MuhAlias[T] = list[T] | T | int).

Let's take a swan dive into the deep end of...

Subscripted Generics: Knock-offs You Can Depend On

@​beartype now propagates child hints up generic type hierarchies. Because @​beartype values the cooperation of coworkers you barely convinced to use @​beartype in the first place, @​beartype propagates child hints efficiently, recursively, and (most importantly) safely. No generics are harmed in the propagation of child hints.

The proof is in the syntactically highlighted rainbow pudding. In this example, we bring the swift fist of justice to Python QA. First, we define a generic parametrized by a type variable T. Next, we annotate a function with a type hint created by subscripting that generic with the child hint int. Finally, @​beartype does the rest by propagating that child hint int into that type variable T up the type hierarchy of that generic.

Behold! Boredom personified, yet you can't turn your lidded eyes away:

from beartype import beartype
from collections.abc import Container, Iterable, Iterator, Sequence

### Define a PEP 585-compliant generic satisfying both the
### "collections.abc.Iterable" and "Container" abstract base classes (ABCs). W00t!
@&#8203;beartype
class SoDumbSoDelicious[T](Iterable[T], Container[T]):
    def __init__(self, sequence: Sequence[T]) -> None:
        self._sequence = sequence
    def __contains__(self, obj: object) -> bool:
        return obj in self._sequence
    def __iter__(self) -> Iterator[T]:
        return iter(self._sequence)
    def __len__(self) -> int:
        return len(self._sequence)

### Define a function accepting an instance of this generic constrained to contain
### *ONLY* integers. Yuppers. It's true. @&#8203;beartype actually type-checks this now.
@&#8203;beartype
def tastes_like_lard(yum: SoDumbSoDelicious[int]) -> int:
    return next(iter(yum))

### Define delectable instances of this generic. Prove this isn't just crazy-talk!
love_me_some_lard = SoDumbSoDelicious((0xCAFE, 0xBABE))
gods_no_more_lard = SoDumbSoDelicious(('Cafe', 'Babe!'))

### Assert that calling this function with a valid parameter returns the
### expected value. You are the cafe. You are the lard. You knew you shouldn't

### have read that, but you kept on doing it. The punchline so wasn't worth it.
assert tastes_like_lard(love_me_some_lard) == 0xCAFE  # <-- oh not that cafe

### Call this function with an invalid parameter, which then raises a
### type-checking violation. May the Gods strike me down if lard is bad for you!
tastes_like_lard(gods_no_more_lard)  # <-- ...you will eat lard and like it

...which raises the expected type-checking violation:

Traceback (most recent call last):
  File "/home/leycec/tmp/mopy.py", line 35, in <module>
    tastes_like_lard(gods_no_more_lard)  # <-- ...you will eat lard and like it
    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^
  File "<@&#8203;beartype(__main__.tastes_like_lard) at 0x7fce8b0828e0>", line 89, in tastes_like_lard
beartype.roar.BeartypeCallHintParamViolation: Function
__main__.tastes_like_lard() parameter yum=<__main__.SoDumbSoDelicious object at
0x7fce8b35d6d0> violates type hint __main__.SoDumbSoDelicious[int], as generic
superclass collections.abc.Iterable[T] of <class "__main__.SoDumbSoDelicious">
index 0 item str 'Cafe' not instance of int.

In other words, @​beartype now "knows" that any object typed as SoDumbSoDelicious[int] should be an iterable container whose items are all integers. Under the furry hood, @​beartype "knows" this by recursively replacing each instance of the type variable T with the child hint int within the generic type hierarchy of SoDumbSoDelicious .

In other words, @​beartype now correctly propagates mappings from type variables parametrizing generic declarations (e.g., the T in class SoDumbSoDelicious[T](Iterable[T], Container[T]):) to the child hints subscripting usage of those generics (e.g., the int in SoDumbSoDelicious[int]). @​beartype does this recursively for arbitrarily complex generic type hierarchies.

This broke my brain and took two full months of mostly unpaid volunteerism. That's why I still beg for money on GitHub Sponsors like that hobo chugging Duck wine out of a cardboard box on your commute to work every day. Like the hobo, I had fun. Like the hobo, I had the urge to sleep in the gutter. This feature was so hard I had to refactor the entire @​beartype codebase to support it – including @​beartype's totally-not-fragile dynamic type-checking code generator that I avoid touching at all costs. That's how totally-not-fragile it is. It's the sort of poorly documented 10,074-line code dump you see prefixed with stultifying ASCII art banners like:

######### LUCK DRAGON EATS YOUR LUNCH, THEN HICCUPS?!? #########
### Here thar be dragons, code matey. Arr. *hiccup*              #

### ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣀⣤⣤⣤⣤⡼⠀⢀⡀⣀⢱⡄⡀⠀⠀⠀⢲⣤⣤⣤⣤⣀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ #
### ⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣴⣾⣿⣿⣿⣿⣿⡿⠛⠋⠁⣤⣿⣿⣿⣧⣷⠀⠀⠘⠉⠛⢻⣷⣿⣽⣿⣿⣷⣦⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀ #

### ⠀⠀⠀⠀⠀⠀⢀⣴⣞⣽⣿⣿⣿⣿⣿⣿⣿⠁⠀⠀⠠⣿⣿⡟⢻⣿⣿⣇⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣟⢦⡀⠀⠀⠀⠀⠀⠀ #
### ⠀⠀⠀⠀⠀⣠⣿⡾⣿⣿⣿⣿⣿⠿⣻⣿⣿⡀⠀⠀⠀⢻⣿⣷⡀⠻⣧⣿⠆⠀⠀⠀⠀⣿⣿⣿⡻⣿⣿⣿⣿⣿⠿⣽⣦⡀⠀⠀⠀⠀ #

### ⠀⠀⠀⠀⣼⠟⣩⣾⣿⣿⣿⢟⣵⣾⣿⣿⣿⣧⠀⠀⠀⠈⠿⣿⣿⣷⣈⠁⠀⠀⠀⠀⣰⣿⣿⣿⣿⣮⣟⢯⣿⣿⣷⣬⡻⣷⡄⠀⠀⠀ #
### ⠀⠀⢀⡜⣡⣾⣿⢿⣿⣿⣿⣿⣿⢟⣵⣿⣿⣿⣷⣄⠀⣰⣿⣿⣿⣿⣿⣷⣄⠀⢀⣼⣿⣿⣿⣷⡹⣿⣿⣿⣿⣿⣿⢿⣿⣮⡳⡄⠀⠀ #

### ⠀⢠⢟⣿⡿⠋⣠⣾⢿⣿⣿⠟⢃⣾⢟⣿⢿⣿⣿⣿⣾⡿⠟⠻⣿⣻⣿⣏⠻⣿⣾⣿⣿⣿⣿⡛⣿⡌⠻⣿⣿⡿⣿⣦⡙⢿⣿⡝⣆⠀ #
### ⠀⢯⣿⠏⣠⠞⠋⠀⣠⡿⠋⢀⣿⠁⢸⡏⣿⠿⣿⣿⠃⢠⣴⣾⣿⣿⣿⡟⠀⠘⢹⣿⠟⣿⣾⣷⠈⣿⡄⠘⢿⣦⠀⠈⠻⣆⠙⣿⣜⠆ #

### ⢀⣿⠃⡴⠃⢀⡠⠞⠋⠀⠀⠼⠋⠀⠸⡇⠻⠀⠈⠃⠀⣧⢋⣼⣿⣿⣿⣷⣆⠀⠈⠁⠀⠟⠁⡟⠀⠈⠻⠀⠀⠉⠳⢦⡀⠈⢣⠈⢿⡄ #
### ⣸⠇⢠⣷⠞⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠻⠿⠿⠋⠀⢻⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⢾⣆⠈⣷ #

### ⡟⠀⡿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⣶⣤⡀⢸⣿⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢻⡄⢹ #
### ⡇⠀⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⡇⠀⠈⣿⣼⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠃⢸ #

### ⢡⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠻⠶⣶⡟⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡼ #
### ⠈⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡾⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠁ #

### ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⡁⢠⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ #
### ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣿⣿⣼⣀⣠⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ #

### Dragons ahoy, code matey. Swab the Steam Deck. Arr.          #
######### ASCII ART MEANS SWEAT, TEARS, AND CI FAILURE #########

It's likely there are unresolved edge cases I'm blissfully unaware of. If you hit one of these golden landmines, please submit an issue. I'll promptly resolve everything or collapse on my keyboard trying. 😰


Plum users may now dispatch on subscripted generics. This is the profit of depending on @​beartype. You wait years for @​leycec to do something. Finally, @​leycec does a thing. Exhaustedly, you pump your hand in the sign of victory. You are vindicated... yet you feel empty.

awkward life moments (brought to you by @​beartype)

Subscripted Type Aliases: Not Just for QA Violators Anymore

@​beartype now propagates child hints up generic type aliases, too. This is genuinely cool. Like, "cool kids" cool. Generic type aliases are kinda like functions or a full-blown templating engine – except for type hints. If you've ever found yourself copy-pasting one stupidly long type hint into another, you've realized you're violating the Don't Repeat Yourself (DRY) principle and are now off the deep end of QA. Thus:

### Instead of copy-pasting hellish type hints surely spawned in the Ninth Circle
### like this...
PygmentsStringToken = list[typing.Union[
    tuple[str | collections.abc.Callable[typing.Concatenate[object, object, ...], object], ...],
    tuple[str | pygments.token._TokenType[str], ...],
    typing.Annotated[collections.abc.Collection[str], beartype.vale.IsInstance[pygments.lexer.include]],
]]
PygmentsBytesToken = list[typing.Union[
    tuple[bytes | collections.abc.Callable[typing.Concatenate[object, object, ...], object], ...],
    tuple[bytes | pygments.token._TokenType[bytes], ...],
    typing.Annotated[collections.abc.Collection[bytes], beartype.vale.IsInstance[pygments.lexer.include]],
]]

### ...just define a single generic type alias like this...
type PygmentsToken[T] = list[typing.Union[
    tuple[T | collections.abc.Callable[typing.Concatenate[object, object, ...], object], ...],
    tuple[T | pygments.token._TokenType[T], ...],
    typing.Annotated[collections.abc.Collection[str], beartype.vale.IsInstance[pygments.lexer.include]],
]]

### ...then subscript that alias with child hints. Look, code metrics! No DRY.
PygmentsStringToken = PygmentsToken[str]
PygmentsBytesToken  = PygmentsToken[bytes]

In this example, PygmentsToken may not look like a function – but it pretty much is. It's a "function" that generates a new type hint from a standardized template every time you subscript it with a new child hint.

So what's the catch? We hope you don't mind requiring Python ≥ 3.12 by dropping support for Python ≤ 3.11. Because... that's the catch. If you try to define even a single type alias in a single submodule of your package under Python ≤ 3.11, your entire app unceremoniously blows up with a fatal SyntaxError: invalid syntax exception. Good luck with that.

@​beartype 0.20.0: because your users hate Python ≤ 3.11, too.

barbarian code never got type-checked by @​beartype

Subscripted Generics + Type Aliases = Power Untold Thrums Through You

I don't even know what "thrumming" is, but I'm pretty sure that's what happens to your sinews when you combine the fearsome power of subscripted generics and subscripted type aliases. The coworker to your right is already cowering. Good. That feeling is good. Soon, they will all kneel! </muhaha— *choking*>

Behold! A subscripted type alias propagating its child hint onto a subscripted generic. Why? Because your QA pipeline wasn't complicated enough and you now need to justify your position to those new bastards in suits nice HR spokespeople:

from beartype import beartype
from collections.abc import Container, Iterable, Iterator, Sequence

### Define the same PEP 585-compliant generic as above. Booooooooring.
@&#8203;beartype
class SoDumbSoDelicious[T](Iterable[T], Container[T]):
    def __init__(self, sequence: Sequence[T]) -> None:
        self._sequence = sequence
    def __contains__(self, obj: object) -> bool:
        return obj in self._sequence
    def __iter__(self) -> Iterator[T]:
        return iter(self._sequence)
    def __len__(self) -> int:
        return len(self._sequence)

### Define a PEP 695-compliant type alias unifying this generic with other stuff.
### Look... *I* don't know. You're the genius here. You'll defly get a raise at

### work if you keep pushing out quality code like this.
type MaybeSoDumbSoDelicious[T] = T | MaybeSoDumbSoDelicious[T] | None

### Define a function accepting either an integer, an instance of this generic
### constrained to contain *ONLY* integers, or "None". @&#8203;beartype type-checks this,

### because @&#8203;beartype has no say in the matter. @&#8203;beartype has to do what you say.
### This is why @&#8203;beartype feels great sadness in the Autumn.
@&#8203;beartype
def maybe_tastes_like_lard(yum: MaybeSoDumbSoDelicious[int]) -> int:
    return next(iter(yum))

### Define delectable instances of this generic. Prove this isn't just crazy-talk!
love_me_some_lard = SoDumbSoDelicious((0x1331, 0xC001))
gods_no_more_lard = SoDumbSoDelicious(('Leet', 'Cool!'))

### Assert that calling this function with a valid parameter returns the
### expected value. You are the cafe. You are the lard. You knew you shouldn't

### have read that, but you kept on doing it. The punchline so wasn't worth it.
assert maybe_tastes_like_lard(love_me_some_lard) == 0x1331  # <-- oh not that cafe

### Call this function with an invalid parameter, which then raises a
### type-checking violation. May the Gods strike me down if lard is bad for you!
maybe_tastes_like_lard(gods_no_more_lard)  # <-- ...you will eat lard and like it

...which raises the expected type-checking violation:

Traceback (most recent call last):
  File "/home/leycec/tmp/mopy.py", line 42, in <module>
    maybe_tastes_like_lard(gods_no_more_lard)  # <-- ...you will eat lard and like it
    ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^
  File "<@&#8203;beartype(__main__.maybe_tastes_like_lard) at 0x7f89162fa8e0>", line 94, in maybe_tastes_like_lard
beartype.roar.BeartypeCallHintParamViolation: Function
__main__.maybe_tastes_like_lard() parameter yum=<__main__.SoDumbSoDelicious
object at 0x7f89165c5810> violates type hint MaybeSoDumbSoDelicious[int], as
<class "__main__.SoDumbSoDelicious"> <__main__.SoDumbSoDelicious object at
0x7f89165c5810>:
* Not int or <class "builtins.NoneType">.
* Generic superclass collections.abc.Iterable[T] of <class
  "__main__.SoDumbSoDelicious"> index 0 item str 'Leet' not instance of int.

...which only goes to show that @​beartype now knows everything. Lo! It is known.

@​beartype is getting bad vibes from trumpy

Subscripted Generics: Gotta Compare Them All

One ill-fated morning, you realise with a gnawing horror... You've run out of coffee. The Pop-Tarts® box is empty. There's no reason to get up anymore. You greet the watery Sun with a thousand-yard stare half-baked beyond the void of space. Yet, through crusty eyes, you think to yourself:

Can I compare whether one subscripted generic is a subhint of (i.e., both commensurable with and narrower than) another subscripted generic? If I can, why would I want to do that? Is this what life without coffee and Pop-Tarts® is like for billions around the world even today!?!?

I don't know why, but unspecified people okay, it's @​patrick-kidger and @​wesselb want to compare subscripted generics. I smell academia and a burgeoning career built on the shuddering back of @​beartype. You might be one of these people or a person like these people. But, let's be honest... you're probably not. You're wondering why you're still reading this and rapidly realizing there's no good answer to that question. Some questions are bad.

Let's pretend you're one of these people. Rejoice! @​beartype now decides the least trivial computational puzzle in the field of type systems, because @​leycec couldn't bear to see that unresolved [Bug] status for another year:

>>> from beartype.door import is_subhint  # <-- it's baaaaaaack
>>> from collections.abc import Sequence
>>> from typing import Generic, TypeVar

### Define some type variables like it's 2017.
>>> S = TypeVar('S')
>>> T = TypeVar('T')
>>> T_sequence = TypeVar('T_sequence', bound=Sequence)

### Define some parametrized generics. Pretend this means something.
>>> class GenericST(Generic[S, T]): pass
>>> class GenericSInt(Pep484GenericST[S, int]): pass

### Decide whether these generics are related or not, especially when subscripted
### by child hints. You don't know why you want to decide this. You just do.

### You've gotta. You're compelled by inner demons to go beyond the pale
### crenellations of normalcy, beyond even the liminal subspaces of QA. WUT?!?
>>> is_subhint(Pep484GenericSInt, Pep484GenericST)
True  # <-- ...okay
>>> is_subhint(Pep484GenericSInt, Pep484GenericST[int, int])
False  # <-- sure, buddy
>>> is_subhint(Pep484GenericSInt[int], Pep484GenericST)
True  # <-- i'm not your buddy, pal
>>> is_subhint(Pep484GenericSInt[int], Pep484GenericST[S, T_sequence])
False  # <-- whatever you say, bear boss
>>> is_subhint(Pep484GenericSInt[list], Pep484GenericST[T_sequence, object])
True  # <-- pretty sure i no longer know what's happening
>>> is_subhint(Pep484GenericSInt[list], Pep484GenericST[Sequence, Any])
True  # <-- seems legit if i squint at it
>>> is_subhint(Pep484GenericSInt[str], Pep484GenericST[T_sequence, S])
True  # <-- it's too good to be true
>>> is_subhint(Pep484GenericSInt[Sequence], Pep484GenericST[list, object])
False  # <-- finally, a falsehood exposed
>>> is_subhint(Pep484GenericSInt[T_sequence], Pep484GenericST)
True  # <-- yeah, right, @&#8203;beartype! as if

I'm confident you could profitably author multiple doctoral theses strung out across multiple poorly remunerated grad students who hate themselves almost as much as you do by just addressing, redressing, and endlessly rehashing this single issue. Papermill careers are built on the boneless backs of issues like this.

Originally, I wanted to bludgeon everyone in attendance with a tiresome essay doing just that. In the bitter end, even the mere thought of tiring everyone tires me beyond the event horizon of exhaustion.

Still, boredom is its own reward. I'll say that @​beartype has probably invented the optimally efficient algorithm for deciding this problem. Nobody cares, of course. Even I am lacklustre about this whole thing.

But a non-recursive depth-first search (DFS) is a glorious self-flagellation that few ever attempt and even fewer survive. This DFS exhibits:

  • Amortized worst-case O(1) constant time complexity. Pump that fist!
  • Non-amortized worst-case O(jk) quadratic time complexity, where:
    • j is the largest number of child type hints transitively subscripting an unerased pseudo-superclass of the first generic passed to is_subhint(). You don't even want to know what "unerased pseudo-superclass" means.
    • k is the total number of transitive pseudo-superclasses of the same generic. Ditto.

Because the DFS is non-recursive, it's stupidly fast, unreadable, undebuggable, and unmaintainable. This is why we @​beartype:

...so that all the suffering is concentrated in one place.

left: parametrized generics. right: ...is that @​leycec?!?

Type Variables: Still Unchecked at Call Time after All These Years

Of course, @​leycec is lazy. @​beartype still lacks general-purpose support for type-checking type variables at call time. This means @​beartype still ignores type-checking violations involving mismatching types across type variables like:

def muh_func[T](muh_arg: T) -> list[T]:
    return ['this is busted', "but you'll never know", 'cause @&#8203;beartype dumb.']

### Beartype should raise a type-checking violation here, as the list returned by
### this function is a list of strings rather than a list of integers. Sadly,

### beartype currently thinks this is fine and does nothing, much like our cats.
muh_func(0xDEADCODE)

@​beartype 0.20.0: shrugging apathetically while your code burns

the answer may shock you

@​beartype-Hostile Packages: @​beartype Tolerates You, Barely

@​beartype now tolerates these extremely popular third-party packages that hate @​beartype ≤ 0.19.0 (and other runtime type-checkers, too):

  • Pydantic. This includes Pydantic-based popular LLM frameworks like:
    • LangChain.
  • urllib3.
  • xarray.

Yes, these packages hate @​beartype (and other runtime type-checkers, too). The irony is especially rich in Pydantic's case, because Pydantic itself is a runtime type-checker. This must be what happens when you go full-Rust.

These packages all doubled down on the typing.TYPE_CHECKING forward reference antipattern, which @​beartype 0.21.0 will have a lot to say about. Until then, this is "Leycec's Abbreviated Notes on the Antipattern That Destroys True Goodness and Heroism":

### This is what PEP 484, PEP 563, and @&#8203;beartype all want you to do. Forward
### referencing external types defined in other packages is easy, fam:
from typing import TYPE_CHECKING
if TYPE_CHECKING:
    import some_package
def muh_func(some_arg: 'some_package.some_submodule.SomeType'): ...  # <-- good!

### Instead, this is what Pydantic, "urllib3", and "xarray" are all doing. This is
### the "TYPE_CHECKING" forward reference antipattern:
if TYPE_CHECKING:
    from some_package.some_submodule import SomeType
def muh_func(some_arg: 'SomeType'): ...  # <-- *BAD*! gag me with a spork, Mork

Those two approaches may look identical. From the runtime perspective, those two approaches share nothing in common. They hate one other. TYPE_CHECKING evaluates to False at runtime, so @​beartype (and other runtime type-checkers) can't see the imports hidden inside those if conditionals. All @​beartype sees is:

  • The absolute forward reference 'some_package.some_submodule.SomeType' in the former case. @​beartype can fully resolve the SomeType type from this. @​beartype is pleased and growls contentedly while rubbing its belly for scritches.
  • The relative forward reference 'SomeType' in the latter case. That's... just not enough information. Like, at all. Throw @​beartype a friggin' bone. @​beartype can't resolve anything from that! @​beartype growls and throws up.

@​beartype 0.21.0 will explicitly detect, warn about, and repair this antipattern across all beartype.claw import hooks. That's the glorious future. For now, @​beartype 0.20.0 contents itself with just silently ignoring these problematic packages in beartype.claw import hooks. We do what we can. Sometimes, it isn't much.

@​beartype doesn't hate these packages. We try to be tolerant of everyone's misinformed and bad opinions. In this case, we tolerate these packages by internally blacklisting them. We don't bother trying to subject their modules, types, or callables to runtime type-checking, because we can't. Their modules, types, and callables despise type-checking. What can you do? Nuthin'. We didn't make crazy; we can't control crazy; we just put crazy in a head lock and roll over it multiple times with our adipose-laden bodies until it stops moving.

Blacklisting these packages really improves the real-world usability of the beartype.claw.beartype_all() import hook in particular, which previously choked on imports from these packages. Since @​beartype now automatically blacklists these packages, you no longer need to manually blacklist them yourself by listing these packages under the BeartypeConf(claw_skip_package_names=...) configuration option: e.g.,

from beartype.claw import beartype_all

### This default call to beartype_all()...
beartype_all()

### ...is now equivalent to this complicated logic, kinda. Yay!
#from beartype import BeartypeConf

#beartype_all(conf=BeartypeConf(claw_skip_package_names=(
###    'pydantic',

###    'urllib3',
###    'xarray',

#)))

@​beartype 0.20.0: make all the bad stuff go away, QA daddy.

lol

Python 3.8: It Died Quietly in the Corner While No One Shed a Tear

@​beartype 0.20.0 officially drops support for Python 3.8. Python 3.8 "recently" hits its official End-of-Life (EOL). Alright. Okay. It was ages ago, wasn't it? I still remember when Python 3.8 was the cool new kid who just wanted to come over and show you his $1,000 LEGO Millennium Falcon constructed entirely from deprecated type hints. Now, it's dead. Only my baldness remains. Python makin' me feel old over here.

This means that Python 3.8 now constitutes a security risk. More importantly, @​beartype hates Python 3.8. Due to the Transitivity of Loathsomeness Principle (probably discovered by Pythagoras the Pythonic, the little-known balding stepchild of Pythagoras the Elder) your codebase now hates Python 3.8 too. Like a bad dream, feature loss is contagious.

$1000 bucks and 47 weeks of your youth: gone, just like @​leycec when the issues pile up

Our New Motto: "We Promise the World and Deliver Less"

@​beartype 0.20.0 delivers less bugs – a lot less bugs. Turns out @​beartype has been buggy for years. If nobody hits a bug for a decade but that bug still exists, does it make a sound when it crushes your codebase at 4:23AM on an icy Sunday? The answer is: "That sound is your dev team screaming in shared anguish."

@​beartype 0.20.0 specifically:

  • Lets you dangerously combine PEP 563 (i.e., from __future__ import annotations) with PEP 695 implicit type parameter instantiation (e.g., def muh_func[T](muh_arg: T) -> T: ...). Against all sanity, this somehow now works:
from __future__ import annotations  # <-- PEP 563, yo. it sucks, but you know best.
from beartype import beartype  # <------- *GOOD*

@&#8203;beartype
def muh_func[T](muh_arg: T) -> T:  # <-- PEP 695 up in here, y'all!
    return muh_arg
  • Lets you dangerously nest PEP 695 type aliases like you just don't care:
from beartype import beartype  # <------- *GOOD*

### PEP 695 type aliases, deeply nested because you no longer care about coworkers.
type ThatFeeling[T] = WhenYoureIn[T] | float
type WhenYoureIn[T] = TooDeep[T] | str
type TooDeep[T] = int | T

@&#8203;beartype
def nuther_func(nuther_arg: TooDeep[complex]) -> TooDeep[bytes]:
    return nuther_arg  # <-- a type-check that is doomed to fail. it ain't so good

The above function signature is equivalent to this simpler, more readable, more maintainable, more debuggable, and yet somehow more boring signature:

@&#8203;beartype
def nuther_func(nuther_arg: int | complex | str | float) -> int | bytes | str | float:
    return nuther_arg  # <-- we can now see this makes less sense than we thought

Boring is bad, though. That's clear. Simplicity doesn't count for much if you're bored all the time. Maximize non-boring even if it costs you your codebase, your career, and your future prospects of a happy family. Do what @​leycec would do.

@​beartype 0.20.0: because @​beartype has to do what you say, even when it no longer wants to

the last thing your code will say before users cry on reddit

Lastly but Beastly (but not Leastly)...

we doin' this

...to financially feed @​leycec and his friendly @​beartype through our ancient GitHub Sponsors profile that predates the existence of dinosaur-like AI chatbots. Come for the candid insider photos of a sordid and disreputable life in the Canadian interior; stay for the GitHub badge and warm feelings of general goodwill.

Cue hypnagogic rave music that encourages fiscal irresponsibility.
🎵 🎹 🎶

Bear Beta Fan Club: Nobody Asked to Join...

...but nobody asked to leave, either. The Bear Beta Fan Club is GitHub's own Hotel California. The "Exit!" sign is poorly labelled. You keep getting roped back in with dubious reassurances that "things will be better this time."

This is that time.

@​posita, @​wesselb, @​iamrecursion, @​patrick-kidger, @​langfield, @​JelleZijlstra, @​RobPasMue, @​GithubCamouflaged, @​kloczek, @​uriyasama, @​danielgafni, @​JWCS, @​rbroderi, @​AlanCoding, @​tvdboom, @​crypdick, @​jvesely, @​komodovaran, @​kaparoo, @​MaximilienLC, @​fleimgruber, @​EtaoinWu, @​alexoshin, @​gabrieldemarmiesse, @​James4Ever0, @​NLPShenanigans, @​rtbs-dev, @​yurivict, @​st--, @​murphyk, @​dosisod, @​Rogdham, @​alisaifee, @​denisrosset, @​damarro3, @​ruancomelli, @​jondequinor, @​harshita-gupta, @​jakebailey, @​denballakh, @​jaanli, @​creatorrr, @​msvensson222, @​avolchek, @​femtomc, @​AdrienPensart, @​jakelongo, @​Artur-Galstyan, @​ArneBachmann, @​danielward27, @​WeepingClown13, @​rbnhd, @​radomirgr, @​rwiegan, @​brettc, @​spagdoon0411, @​helderco, @​paulwouters, @​jamesbraza, @​dcharatan, @​kasium, @​AdrienPensart, @​sunildkumar, @​peske, @​mentalisttraceur, @​awf, @​PhilipVinc, @​dcharatan, @​empyrealapp, @​rlkelly, @​KyleKing, @​skeggse, @​RomainBrault, @​deepyaman, @​mzealey, @​adamtheturtle, @​Moosems, @​minmax, @​jedie, @​pablovela5620, @​thiswillbeyourgithub, @​Logan-Pageler, @​knyazer, @​ilyapoz, @​yuzhichang, @​Fedezzab, @​antonioan, @​im-Kitsch, @​mthramann, @​fbartolic, @​rgallardone, @​frrad, @​jonnyhyman, @​jennydaman, @​likewei92, @​acec2127, @​Glinte, @​rudimichal, @​woutdenolf, @​PauloHMTeixeira


Configuration

📅 Schedule: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@renovate renovate bot added the dependencies Pull requests that update a dependency file label Mar 5, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
dependencies Pull requests that update a dependency file
Projects
None yet
Development

Successfully merging this pull request may close these issues.

0 participants