Beartype 0.17.0: Ultrabear vs. Mecha-Bugbear #317
Replies: 4 comments 4 replies
-
Happy birthday @leycec Congrats for this huge release that I'm so eager to use 😃 |
Beta Was this translation helpful? Give feedback.
-
Congrats! I've sent you some love for the amazing work your do (via sponsorship). |
Beta Was this translation helpful? Give feedback.
-
Awwww. Thanks so much for the outpouring of kindness, everybody. You and Ultraman are why I commit. @beartype is here to sandblast the festering hive of bugs from your newest AI-prompted app before middle management finds out and escalates to the project manager with the rheumy eyes, whose name is probably Andy. Relatedly: I'm devoting the remainder of January to finally migrating from a wheezing Windows 95-era Athlon II dust collector to a new Arch-based 7900X3D dev beast that purrs menacingly. If I fail to commit for a few days, you know what happened. The new machine isn't posting and I'm beating my arthritic knuckles in futility against the motherboard. That always works... doesn't it, @posita!? 🥲 |
Beta Was this translation helpful? Give feedback.
-
Beartype 0.17.1 gently descends from the heavens on a golden dragon made of rainbows. "How can this be!?", the crowd exclaims. Verily, it is best not to ask questions: pip install --upgrade beartype This patch release comes courtesy these proud GitHub Sponsors, without whom @leycec's cats would currently be eating grasshoppers:
Thanks so much, masters of fintech. Who Let the Bugs Out?This patch release adds explicit support for Specifically, for each
For example: from __future__ import annotations # <-- PEP 563: it makes kittens cry
import typing
# When you said this...
class MuhTuple(typing.NamedTuple): # <-- very reasonable
muh_field: int # <-- makes sense, huh?
# ...what "typing.NamedTuple" heard you say was this:
class MuhTuple(typing.NamedTuple):
def __new__(cls, muh_field: ForwardRef('int')) -> None: # <-- wut
self.muh_field = muh_field
MuhTuple.__new__.__module__ = 'named_MuhTuple' # <-- lolbro Why does @beartype now responds with a mountain of code that took us two weeks. Was that worth it? Probably not. Probably should have just implemented deep type-checking already. Instead, this is all we got. Mo Money Means Mo Code@beartype 0.17.0 advised everybody to donate money to charitable causes. Instead, everybody donated more money to @beartype. Reverse psychology surely is the path of righteousness. These monocled code aristocrats graciously filled the money trough, which the cats are now sleeping on against our wishes:
Lastly, @beartype thanks Bully Maguire for saving New York City with sassy hair, emo eye shadow, and impromptu cafe street dancing.
|
Beta Was this translation helpful? Give feedback.
-
Team Tokyo Bear presents... Ultrabear vs. Mecha-Bugbear, the titanic struggle of ultimate opposites. On the left, @beartype 0.17.0 in the hybrid static-runtime type-checking corner. On the right, the voracious bugs proliferating throughout your codebase in adorable collectable card format.
vs.
a @leycec in the paw is worth two in the mouth of mecha bear
There can be only one victor in your
git
log.Wait. What's Happening Here?
@beartype 0.17.0 is a-go-go:
@beartype 0.17.0 descends like Ultraman King german-suplexing Absolute Tartarus onto Tokyo Tower for only like the fifth time. How many times can society rebuild Tokyo Tower before learning to accept that that thing's just a Kaiju magnet for dark monster forces from a mirror pocket universe? Some buildings are better left un-built.
Wait. What were we debating again? Incoherent monologues about Ultraman power levels can only mean one thing:
when you're straddling a giant fish head in the canadian rockies and conehead sumo baby just wanna play
But first...
A Round of Applause for the Homies
We give thanks. I'm humbly and hugely grateful to everyone who's ever financially supported @beartype via GitHub Sponsors. I'm especially grateful to our generous lifetime donors who almost gave a kidney for @beartype. These are @beartype's Three Biggest Fat Bear-cats:
jaxtyping
support? We do that. Equinox support? That too. If it's a @patrick-kidger byproduct, @beartype probably now hawks it on Etsy.@beartype supporters fight for you. Their username is legend.
@langfield is... Some Dude in a Skintight Rubber Suite.
also featuring @patrick-kidger (left) and @KyleKing (right)
also featuring all your codebase bugs (kaiju gettin rocked)
pytest-beartype
: It's a Steaming Hot Thing, NowDevtools superstar Tushar Sadhwani (@tusharsadhwani) saves everyone's QA bacon with
pytest-beartype
, @beartype's newest official package. If you always wanted to test-drive @beartype but were too afraid to risk becoming homeless when the whole thing backfired on your last working production server, letpytest-beartype
confine @beartype to just yourpytest
-based test suite. Who cares ifpytest
burns down, am I right? Anyone?Let's begin:
Install this steaming hot thing:
Configure this still-steaming hot thing before it goes lukewarm. You have two choices here, depending on whether you prefer passing temporary command-line options or writing permanent configuration files:
Pass the new
--beartype-packages='{package_name1},...{{package_nameN}'
command-line option to thepytest
command, where--beartype-packages
is a comma-delimited list of all package names to beinfestedpollutedsulliedthrottledtype-checked by @beartype:Modify your existing top-level
pyproject.toml
configuration file with a new[tool.pytest.ini_options]
section resembling:For those who love CLI warrioring but hate POSIX-compliant shell syntax,
bash
, u make me hurt inside also check out @tusharsadhwani'szxpy
: a Python +bash
mashup that basically throws out the entirety ofbash
. Okay. So, it's more a beatdown than a mashup, really. Bash thatbash
up, Python!pytest-beartype
surveys all it has done. conclusion: "this is fine"Beartype: Phase I: Roman Numerals Means Things Just Got Serious
@beartype 0.17.0 hallmarks the end of Beartype: Phase I. The central theme here was shallow type-checking (i.e., type-checking that objects are of the expected types without recursively type-checking any items contained in those objects).
Let's recap in slow-mo. Like that inevitable filler ep where your favourite TV show reboots itself after a five-year gap with all new child actors and a reprehensible script seemingly authored by lizzid people, @beartype wasn't always this
gooddecentacceptable.@beartype once raised exceptions when confronted with complex, non-standard, or otherwise disreputable type hints. Now, with @beartype 0.17.0, @beartype either passively accepts literally anything you can throw at it by doing nothing or generates shallow or deep type-checking code validating that thing. @beartype no longer explodes. Instead, @beartype permissively tolerates a QA-breaking world full of strife and typing monstrosities it can never fully comprehend.
@beartype is now a Jack-of-All-QA-Trades. @beartype didn't know jack before. Now, @beartype know jack.
@beartype 0.17.0: "Extruded alien protein chunks in my dinner?"
Beartype: Phase II: Roman Numerals Embiggen
@beartype 0.17.0 also hallmarks the beginning of Beartype: Phase II. The central theme here is deep type-checking (i.e., type-checking both that objects are of the expected types and recursively type-checking some or all items contained in those objects). Now that @beartype shallowly type-checks almost everything, it's time to dive into the deep end. Over the course of 2024, @beartype will gradually roll out:
O(1)
constant time.O(n)
constant time. When we do this, we'll couple this to an actual deadline scheduler preventing @beartype from consuming more than some preconfigured ratio of wall-clock time.You may now be thinking:
...heh. Let's begin.
@beartype 0.17.0: "Mutated alien alligators ain't no thang."
Beartype 0.17.0: The Interquel We All Deserve
@beartype 0.17.0 massively increases the configurability of @beartype. Because everybody always wanted to:
violation_*type
, then win.violation_*type
grepping intensifies.__instancecheck_str()
enters the chat emboldened and swaggering.violation_verbosity
+BeartypeVerbosity
is snickering in the back.type {name} = {hard_stuff} | {moar_stuff}
.hint_overrides
+BeartypeHintOverrides
. It's best not to question this stuff.These improvements were made possible only by the code-bending thaumaturgy of Montreal API snow wizard @felixchenier (Félix Chénier), who exhaustedly pushed numerous pull requests (PRs) across the
git
finish line. @beartype is now something actually usable by living humans that breathe oxygen. As a token of our gratitude, please accept this animated Ultraman GIF.@felixchenier (right) threatens bugs (offscreen) as @leycec (left) supports
To exhibit the fearsome level-up that is @beartype 0.17.0, we now present...
The Flux Beartyper
Like everyone, I used to hate the universal
beartype.claw.beartype_all()
import hook up until five minutes ago. By default,beartype.claw.beartype_all()
dangerously raises fatal exceptions on type-checking violations that occur anywhere in your full app stack – including in code you do not own, have no control over, and mostly could care less about. But what if you could configurebeartype.claw.beartype_all()
to instead emit non-fatal warnings rather than destroy your entire app due to somebody else's sins?Thankfully, it happened. I slipped off a toilet while hanging a clock shaped like a hibernating bear, banged my head on a towel rack shaped like a spawning salmon, and... I saw it there. The Flux Beartyper:
That's it. That's the Flux Beartyper. This K-k-k-killer Combo compels @beartype to:
The Flux Beartyper is thus the superset of mypy and
pyright
: it does everything those guys do (complain about everything), while also doing something those guys can never do (actually enforce something). Moreover, it selectively enforces those things only on the one thing you have under your total control: your own codebase.in the endless struggle of bad versus good code, only roundhouse chops to the scaled carapace will decide the fate of your investment portfolio
BeartypeConf
Explodes with Greasy New Possibilities@beartype configurations just got a whole lot embiggened. Since the lesson of my childhood is that bigger is always better, we feel happy about this startling explosion of unmaintainable technical debt and bewildering code complexity.
is that what our lives have come to
violation_*type
: When You Know Better, You Better Tell @beartypeBy default, @beartype raises thoughtful but eyebrow-raising type-checking violations with exception types like
beartype.roar.BeartypeCallHintParamViolation
andbeartype.roar.BeartypeCallHintReturnViolation
. Fine-grained granularity. That's just great... isn't it?But what if you hate that? What if you love coarse-grained generality instead? What if you really just want @beartype to raise
TypeError
exceptions on type-checking violations like everything else in the bloody runtime type-checking community already? Previously, those users had to grit their teeth until grinding their molars down into stubs. You know, what does "grit teeth" even mean? Why can you grit teeth but not anything else? I've always wanted to grit my toes. Can't do it. Grit my lips? It's right out.For those who are about to grit their keyboards, @beartype 0.17.0 introduces a new secret brotherhood of
BeartypeConf
options governing the types of violations it produces:violation_type
, the default type of exception raised by @beartype when a type-checking violation occurs – any type-checking violation, including:die_if_unbearable()
violates a type-check.@beartype
-decorated callable violates a type-check.@beartype
-decorated callable violates a type-check.Most users who want to configure violations want to pass this option. Defaults to
None
, in which case @beartype preserves backward compatibility by just doing what it currently does – which is perfectly fine, of course. No shade on @beartype defaults. Obsessive-compulsives may also fine-tune:violation_door_type
, the type of exception raised by @beartype when an object passed todie_if_unbearable()
violates the passed type hint. Since @beartype type-checks PEP 526-compliant annotated variable assignments (e.g.,godzilla: Sequence[KaijuThatHateTokyo] = Gojira('RAAAR!')
) by internally callingdie_if_unbearable()
, this is also the type of exception raised when an annotated variable violates its type hint. Defaults tobeartype.roar.BeartypeDoorHintViolation
.violation_param_type
, the type of exception raised by @beartype when a parameter violates its type hint. Defaults tobeartype.roar.BeartypeCallHintParamViolation
.violation_return_type
, the type of exception raised by @beartype when a return violates its type hint. Defaults tobeartype.roar.BeartypeCallHintReturnViolation
.violation_type
is merely a convenience enabling users to trivially control theviolation_door_type
,violation_param_type
, andviolation_return_type
parameters without having to explicitly pass all three of those parameters.Pretend that @beartype is normal. It feels good and @beartype can no longer complain:
does that dude in the back really have drills for arms? really? so cool
violation_*type
: When Exceptions Are Too Scary for the Slumber PartyWarnings are exceptions in Python. I know, right? Who knew. All these years. The builtin
Warning
class subclasses the builtinException
class. Oddly, this implies that warnings are technically raisable as exceptions. They are, but you shouldn't. Warnings should only be emitted withwarnings.warn()
.You know that – but does @beartype? Does @beartype acknowledge this distinction? Imagine me now saying: "Nope. @beartype sucks. It just raises warnings like exceptions." That... would be a pretty bad look. Even Ultrabear would frown. That is why I am now instead saying the opposite.
@beartype rules! When you pass a
Warning
subclass as aviolation_*type
option, @beartype detects that as your attempt to emit non-fatal warnings from type-checking violations and then does so by dynamically generating type-checking code that callswarnings.warn()
.Is there a real-world application? There are so many I cannot count them all on my vestigial drill hands. Pretending that @beartype is mypy is one. This is another: gradual adoption.
Imagine a monolithic codebase named
FuglyBugs
that hates you. That codebase is a sprawling million-line ghetto of badly typed spaghetti whose most inventive feature is single-letter attribute names in the Unicode Tertiary Ideographic Plane. You're the new guy next to the gurgling water cooler that leaks suspicious fluid all over the floor according to a Poisson distribution with a high λ. It's do or die. The garbage is piling up in the corridor. Your grizzled landlady is hissing about "overdue rent" or something. Who cares, landlady? But the cats are hissing as well. You can't throw @beartype directly at that codebase without destroying your nascent life story. So what a somber devops goin' do?You gradually adopt a QA bear cub today, the @beartype way:
Of course, you can pass an app-specific
UserWarning
subclass rather thanUserWarning
. And... you should probably do that.FuglyBugs
! spit it! who did this 2 u!?violation_verbosity
+BeartypeVerbosity
: Massage Your Brain with BeartypeLet us breathe out and then back in and then... actually please keep doing that. Let it never be said that @beartype gives bad advice. Wait. What is this, a yoga class in our release notes? What were we talking about again? Which is an appropriate lead-in to...
Verbosity. Prior versions of @beartype were verbose (like this changelog). Type-checking violation messages included the full contents of the current beartype configuration, which now contains an infinite "wealth" of options.
@beartype 0.17.0 curtails that insanity by dialing down on the prolix nebulosity. Beartype configurations are no longer embedded in violations by default, because your sanity is our personal responsibility. Somebody hates this change and is now thinking: "But I like the old way. I like parking tickets, too."
@beartype 0.17.0 is here for that somebody, introducing a new
violation_verbosity
option that governs violation verbosity. The value of this option is one of the following members of our newbeartype.BeartypeVerbosity
integer enumeration:BeartypeViolationVerbosity.MINIMUM
, intended for end users potentially lacking core expertise in Python. Babies, in other words. This is for babies.BeartypeViolationVerbosity.DEFAULT
, intended for a general developer audience assumed to be fluent in Python but vengeful on GitHub. tentatively raises handBeartypeViolationVerbosity.MAXIMUM
, extending the default verbosity with additional metadata intended for inadvisable all-nighter debugging sessions that end in shaking, weeping, and puffy cheeks. This includes:violation_verbosity
defaults toBeartypeViolationVerbosity.DEFAULT
, because your brain is a precious quantity. But you know better.never trust a mechanized triceratops named Yapool is all I'm sayin'
hint_overrides
+BeartypeHintOverrides
: Who You Gonna Believe?Have you ever wanted to lie to your userbase, static type-checkers, other runtime type-checkers, and document generators alike? Now you can.
Let's back up. PEP 484 – the standard named "Type Hints," so that's probably what it's about – included this bizarre substandard see wut i did there named the implicit numeric tower. The idea was simple, albeit horrible. Static type-checkers would just globally replace all:
float
types in type hints withfloat | int
unions.complex
types in type hints withcomplex | float | int
unions.That's fine. When your significant other says that, it's absolutely not fine. This is like that. Because globally replacing types in type hints without user consent which then reduces numerical precision isn't actually that cool. So, @beartype only conditionally supports the implicit numeric tower. If you want us to do that, that's cool, but you have to opt in by enabling
is_pep484_tower=True
.So far, so good. But what about third-party scalars published by packages like NumPy and SymPy? Third-party scalars don't subclass builtin scalars (e.g.,
numpy.int_
does not subclassint
). But real-world "tough guy" data science and machine learning mostly uses third-party scalars rather than builtin scalars. The implicit numeric tower is thus obsolete for most of us. Sadness overflows my pewter mug shaped like a grizzly bear.So... what now, PEP 484? Huh? Let @beartype 0.17.0 tell you what now.
@beartype 0.17.0 introduces yet another outrageous new
BeartypeConf
option:hint_overrides
, whose value is abeartype.BeartypeHintOverrides
instance mapping source to target type hints. And...BeartypeHintOverrides
is an in-house immutable dictionary type (i.e., pure-Python @beartype-specific implementation of a hypotheticalfrozendict
builtin), enabling the memoizedBeartypeConf
dataclass to accept dictionaries while preserving caching. And... @beartype globally and recursively substitutes all type hints that are keys of thehint_overrides
dictionary with their corresponding values. And... can this get any more complicated? The answer is: "Yes." You feel very tired.hint_overrides
is a generalization of the implicit numeric tower. Like the implicit numeric tower, you're lying to everybody. Unlike the implicit numeric tower, your lies are no longer constrained to what PEP 484 fed you; you can now lie about everything. And you should! Lies are healthy. "@beartype said so."Crazily, Python has no official frozen dictionary type. @beartype had to make up its own. I grunt meaningfully and then point back to the chalkboard, which now resembles a Cthulhian nightmare of random scribbling from beyond the realm of sleep.
Since
hint_overrides
generalizes the implicit numeric tower, you can now explicitly express the implicit numeric tower by instead passing:B-b-but what if you want to actually do something useful? Specifically, what if you want an API typed as matching only builtin scalars to also transparently match third-party scalars? Behold! You weave a web of tangled lies, but it all works out in the end. You boost your end-of-life karmic score alot higher than all those beleaguered champions who stoically fight for justice:
Users don't understand what
numbers.Integral
means. But users do understand whatint
means. Okay. Not all users. A majority of users. Okay. Not even that. A few users understan... Okay. You understand whatint
means.Meanwhile, NumPy, SymPy, and everybody else understands what
numbers.Integral
means. They register their own third-party scalars with PEP 3141-compliant abstract base classes (ABCs) defined by the standardnumbers
module, also referred to as the "explicit numeric tower".By instructing @beartype to internally replace all builtin scalar types like
int
with corresponding ABCs in the explicit numeric tower likenumbers.Integral
, you have made your own Ultimate Implicit Numeric Tower: an implicit numeric tower that actually works, because it actually supports stuff you care about. Because you know best. You do you. Now, @beartype does too. wait, what does that one-liner actually meancaring means punching a monster in the gut for humanity
Beartype Presents... The Félix Chénier Connection
Let's put all of the above together. Courtesy Félix Chénier, the Université du Québec à Montréal du Canada du Planet Earth du Milky Way Spiral Galaxy du Material Universe mad lad that made all this possible, @beartype presents the Big and Tall @beartype configuration:
Configure @beartype like Félix Chénier would. The feeling of power is delicious, yet kinda indescribable.
so this is what feels like when monsters cry
Violation Readability: The Tesseract Unfolds
@beartype once raised hair-raising type-checking violations with messages straight outta the Seventh Circle of Typing Hell. Wince and cringe as your third eye bleeds from its calcified perch in the pineal gland!
Indeed, the dawn of
typing
prehistory was a dark time. Those days are long behind us, though. @beartype 0.17.0 now delivers two new extensible APIs for generating readable messages designed by you, consumed by your userbase, and complained about relentlessly on your issue tracker. @beartype is no longer responsible for anything! I'm weeping with joy here.@beartype: just some rando in a skin-tight rubber suit after all
__instancecheck_str__()
: Just Do It Yourself, Because Our Way Sucked@beartype 0.17.0's unleashes our first official plugin API: the
__instancecheck_str__()
protocol, a new double underscore method accepting a single object that violates the current class and returning a human-readable substring describing that violation.__instancecheck_str__()
intentionally shares a similar name with the standard__instancecheck__()
dunder method; both are defined on the metaclass of a class. Whereas__instancecheck__()
returns abool
, however,__instancecheck_str__()
returns astr
. The full signature resembles:This will soon make sense. Would I lie? This is your final API for raising human-readable violations:
...which now raises the humane violation:
Simple, right? And it actually is. Caveats may apply. Notably, the string you return from your
__instancecheck_str__()
implementation:__instancecheck_str__()
is intended to be supported by competing runtime type-checkers (e.g.,typeguard
, Pydantic) as a pseudo-standard. Naturally, ain't nobody got the time to write an actual PEP for that. Pseudo-standard.just another day at the kaiju office ends on a terrifying footnote
PEP 695: Only the Third Time Python Standardized Type Aliases
@beartype 0.17.0 now sorta supports PEP 695-compliant type aliases (i.e., type hints with simple names whose values are other more complex type hints, instantiated by statements of the form
type {alias_name} = {alias_value}
under Python ≥ 3.12). Type aliases are useful for improving the readability of type-checking violations. Type aliases are also mostly broken by runtime CPython deficiencies. One out of two ain't bad.The
numpy.typing.ArrayLike
union is the canonical use case. Shield your child's eyes from this abomination beyond from the Chaos Gate:Now imagine – in the vast, cavernous, and crawling darkness beyond your eyelids – what happens when you annotate
@beartype
-decorated classes and callables withnumpy.typing.ArrayLike
. Thankfully, you don't even have to imagine:Tell me that you don't understand what I'm saying without telling me that you don't understand what I'm saying, @beartype.
Now consider this human-readable alternative that truncates the above abnormal outgrowth of code logorrhea into something even Ultrabear's mother could love:
Saner. Terser. Arguably, even readable. @beartype still explains the violation without lore-dumping the squalid guts of
numpy.typing.ArrayLike
, which is now abbreviated to simplyArrayLike
.Caveats apply, because this is @beartype. Due to character flaws beyond my control (...video games is what I'm saying), @beartype currently only partially supports
type
aliases. Notably, @beartype:Fully supports type aliases containing neither forward references nor recursion. Bears cheer!
Conditionally supports global type aliases (i.e., defined as global attributes at module scope) containing forward references but not recursion under the proviso that you only automatically apply @beartype via its
beartype.claw
import hooks. If you manually apply @beartype via the@beartype.beartype
decorator, however, @beartype will raise exceptions on encountering any type aliases containing forward references. Why? Because PEP 695 is fundamentally broken and lies about everything. More bears half-heartedly cheering while crying at the same time.Cannot support local type aliases (i.e., defined as local attributes in callables) containing forward references. Sadly, no subsequent @beartype release is expected to support this use case. For unknown reasons that would probably bore and anger all of us in equal measure, CPython's runtime implementation of local annotation scopes is fundamentally, irredeemably, and profoundly broken. Thus, bears cry.
Does not support type aliases containing recursion. Unlike the prior bullet point, @beartype can theoretically fully support this use case. It simply chooses not to at the moment, because it is very tired and must now lie down. A subsequent @beartype release is expected to fully support recursive type aliases. Unenthusiastic bears roll around on your front lawn.
You may now be thinking: "Uhh... how can @beartype support type aliases containing forward references declared at global but not local scope?" Actually, who am I even kidding? Nobody cares. The audience for
type
aliases consists of two Capuchin monkeys that mostly just chortle as they tickle one another and a microdosing banana. For them, allow my disillusioned younger self to copy-paste himself from @beartype'sgit log
:Wait. I can hear the Capuchin monkeys and microdosing banana ruminating already: "If PEP 695 lacks runtime support for forward references, then how does @beartype actually support global type aliases containing forward references?" Thank you, banana-monkey. I'll take it from here.
This is where
beartype.claw
import hooks come in. When you apply import hooks, what you're really doing is applying abstract syntax tree (AST) transformations that transmute your crippled-by-design CPython code into an entirely new language of our own devising: pybearthon. Pybearthon could mostly care less whether or not CPython itself is broken, because pybearthonwalksslithers its own way. In this case, pybearthon silently transforms...Because PEP 695 is fundamentally broken, that same AST transformation fails at local scope with insane exceptions like:
Ergo, runtime type-checkers can only support global type aliases containing forward references. You now regret your frank line of questioning, banana-monkey.
An even worse caveat applies, however. Yet again, it's not @beartype's fault. Python ≤ 3.11 hates
type
aliases. By "hates," I mean "Python ≤ 3.11 raises non-human-readableSyntaxError
exceptions at bytecode generation time on attempting to import any module containing even a singletype
alias regardless of where in that module thattype
alias is." This isTrue
hatred: a new plateau of hate-filled overkill hitherto unknown to your handlebar mustache-twirling boss.Even if you try to hide
type
aliases from older Python versions behindif
conditionals likeif sys.version_info >= (3, 12):
, your circumlocution fails. Then, at last, you know @leycec to be an insufferable oracle of horrible truth. Hiding doesn't work. You either need to:type
aliases across your entire codebase to a unique submodule conditionally imported only under Python ≥ 3.12.type
aliases with theexec()
builtin hidden behind cleverif
conditionals likeif sys.version_info >= (3, 12):
. This is the lazy way. Thus, this is what we do below.You are now thinking: "
type
aliases seriously suck, dude. Seriously." Allow me to now dispel all your justifiable fears with edgelord code that should make you cringe. I say, "Embrace the cringe." Let's goooooooooooooo:So who is going to use
type
aliases if you can't use them under Python ≤ 3.11 without soul-destroying boilerplate and can only use them under Python ≥ 3.12 subject to a litany of context-sensitive caveats that even your family lawyer who routinely represents banana-monkeys can't get right, exactly?Nobody. The answer is nobody.
that feeling when you realize you wasted ten minutes of your life
Unrecognized Subscripted Builtin Type Hint: Yeah, We Do That Too
A brief history in futility and the sound of one keyboard clapping.
A decade (but what feels like a lifetime) ago, CPython devs made the ignominious decision to externalize all type hints for the standard library into a third-party package inaccessible to runtime type-checkers named
typeshed
. According to the Python mailing list, "Accurate typing is hard!" I am now heaving my emaciated arms up into the air. You already did the hard work in thetypeshed
, CPython devs. Can't you literally copy-paste type hints from thetypeshed
into the standard library? How hard is repeatedly hitting two key chords on a keyboard? This isn't rocket science or even figuring out how to bottle-feed a disgusting slurry of processed fish guts to a vicious Bengal cat with a deplorable attitude stricken by Calicivirus. That was rocket science. This is only disappointment.To compound matters, CPython,
typeshed
, andmypy
authors (whose Venn diagram is a perfect circle) quietly collude to implement non-standard type hints. The way this nefarious social network works is pretty simple: it's absolutely not simpletypeshed
devs intend to annotate something in the standard library for which no standard type hints exist.__getitem__()
dunder method) without documenting anything or standardizing the semantic meaning of the resulting object.mypy
devs quietly interpret subscription of that type as a non-standard type hint in an obscure and undocumentedmypy
-specific manner.typeshed
devs annotate things in the standard library using those non-standard type hints in an obscure and undocumentedmypy
-specific manner.I'm not bitter. I just look bitter. My face is permanently frozen into a weatherbeaten rictus of crag lines, worry warts, and wrinkle canyons.
The point is that weird undocumented type hints internally used throughout the CPython community now exist. Is that a problem for us? Yes. Their avoidance of the PEP standards process makes this our problem. The @beartype userbase has somehow become aware of and now wants us to support these weird undocumented type hints – including:
weakref.weakref[...]
type hints. Pretty obvious what that semantically means, right?weakref.weakref[str]
is a weak reference to a string, for example. Makes sense. But then what about...os.PathLib[...]
type hints. "uhh wat?" Yeah. It's undocumented, but you can actually subscript the standardos.PathLib
type by another type. The semantic interpretation is not at all obvious; since nothing is documented, I had to reverse engineer the semantic interpretation by grepping themypy
issue tracker for a hot minute. Surprisingly, this is whatos.PathLib[T]
means:The point is that this sucks. Because this sucks, @beartype 0.17.0 now shallowly type-checks all weird undocumented type hints internally used throughout the CPython community by reducing those hints to their origin class (i.e., by just stripping subscription from those hints). For example, @beartype now reduces:
weakref.weakref[T]
type hints to theweakref.weakref
class.os.PathLib[T]
type hints to theos.PathLib
class.@beartype 0.17.0: we support bad stuff, because we support you. Wait... That one-liner kinda didn't sound right. You're not bad stuff. You're awesome sauce. But your awesome sauce needs bad stuff. Let's try this one more time.
@beartype 0.17.0: all the bad stuff, now in one easy convenient package.
only a flying water bottle on fire holds the key to planetary salvation
Isomorphic Non-closure Wrapper: Changelog from Hell Continues
ohmygodswontthischangelogquitalready
Apparently, this is fine now:
When a changelog just needs to stop already, animated Ultraman GIF.
Cobra Guy encourages at-risk codebases to try a bit harder
@patrick-kidger: If He Didn't Type It, You Shouldn't Use It
Lastly, @beartype 0.17.0 officially:
Supports @patrick-kidger's Equinox now. JAX + ML + @beartype = pretty sure you just got a raise.
Does not support
nptyping
, because (A)nptyping
is dead and (B)nptyping
is bad. Dead and bad are both bad. If you attempt to usenptyping
-based type hints under @beartype ≥ 0.17.0, @beartype will raise fatal exceptions informing you that you are bad by association: e.g.,What @beartype is saying is that
nptyping
is currently unmaintained, suffers over 20 severe issues, and emits 7 severe NumPy deprecation warnings.nptyping
is a ticking time bomb about to explode your codebase into radiating black bodies. Bear bros don't let bear bros importnptyping
. Not even once.Thankfully, @patrick-kidger exists. hard to prove, but likely true Everybody wants to transition from
nptyping
to @patrick-kidger's Google-adjacentjaxtyping
package already. Unlikenptyping
,jaxtyping
provides well-maintained type hints covering NumPy, JAX, PyTorch, and TensorFlow. It's kinda intense. It's also @beartype's official FAQ recommendation for type-checking NumPy arrays.The data science pipeline you save might just be your own. It's probably too late, though.
your codebase when you realize you used
nptyping
everywhereBiosecurity Tickets: No Longer Something I Grovel on the Floor For
Previously, I publicly begged for GitHub Sponsors with a flashy logo believed to be reminiscent of sketchy underground raves – which, of course, none of us know anything about or have ever attended. awkward collar tugging
Instead, I now implore you to donate that same money to local charities, food banks, animal shelters, homeless shelters, and every other institution that does tangible real-world good. Humanity isn't doing so h0t in 2024. We don't talk about that, because we're supposed to do fun here. Coding is our wheelhouse.
But it's largely institutions – both corporate and governmental – that probably should have supported open-source initiatives like @beartype. That should never have fallen on the heavy shoulders of individuals. As actual persons, our responsibilities are mostly to the local: local lives, local families, and local communities.
I'm not closing my GitHub Sponsors, because I put too much work into that flashy logo and now I'm emotionally invested. But I am beginning to moulder and stew in my brain sauces and think:
I don't know. I'm just a gawping code monkey that loves Ultraman. But I do know that the solution almost certainly involves gifting turn-based strategy and role-playing and Yakuza videogames on Steam to @leycec.
@beartype userbase: make a better world through glowing orbs
To End a Changelog: Greets to the Greatest
Thanks so much, everybody. These bear bros went above and beyond the mating call of Spring to deliver a better QA UX for us all:
@langfield, @patrick-kidger, @KyleKing, @felixchenier, @posita, @wesselb, @justinchuby, @EtaoinWu, @kaparoo, @kasium, @peske, @tvdboom, @MaximilienLC, @fleimgruber, @alexoshin, @AdrienPensart, @uriyasama, @sunildkumar, @mentalisttraceur, and @skeggse, @reikdas, @mentalisttraceur, and @tusharsadhwan – may your usernames reign supreme and then lord that supremacy over all of us.
These GitHubbers hugged @beartype by starring it recently. Little did they know, but they were inviting a disastrous chain of consequences culminating in my now pinging them:
@Vol0kin, @alimoezzi, @phsilva, @shyamsn97, @antoniomdk, @procore, @hbakri, @dcharatan, @motherwort, @jxu, @doraut, @joaoapel, @AntoineD, @double-thinker, @tcl326, @Guiforge, @Valendrew, @jeffswt, @lxdlam, @padraic-shafer, @bzczb, @xhiroga, @justincase-jp, @thapecroth, @ZachariahPang, @axrn, @mjwen, @calebj, @Holocor, @anagri, @nhanarT, @dfundingsland, @DavidHernandez21, @namurphy, @makaronma, @urimandujano, @damtien444, @deutschmn, @Fizzadar, @AnjieCheng, @Bullish-Design, @ericfeunekes, @cameronraysmith, @sfrieds3, @garthk, @LiamBrenner, @abecciu, and @moddyz – may @beartype 0.17.0 give you everything you've always wanted except deep type-checking of dictionaries.
It's my birthday tomorrow. I will be eating cake covered in ice cream and playing video games all day. Thank you. May @beartype bless your codebase.
@beartype feels that feeling, too
Beta Was this translation helpful? Give feedback.
All reactions