Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
**Beartype 0.17.0:** Ultrabear vs. Mecha-Bugbear
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. ![](https://wikizilla.org/w/images/0/04/Cyber-Bear.png?20170106001251) **vs.** ![](https://static.wikia.nocookie.net/lil-alchemist/images/2/26/Mecha_Bear.png/revision/latest?cb=20150531014124) <sup>a @leycec in the paw is worth two in the mouth of mecha bear</sup> There can be only one victor in your `git` log. ## Wait. What's Happening Here? **@beartype 0.17.0** is a-go-go: ```bash pip install --upgrade beartype ``` @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: ![](https://media1.tenor.com/m/f8tqq33LZ34AAAAC/ultraman-fight.gif) <sup>when you're straddling a giant fish head in the canadian rockies and conehead sumo baby just wanna play</sup> 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](https://github.com/sponsors/leycec). I'm especially grateful to our generous lifetime donors who almost gave a kidney for @beartype. These are @beartype's Three Biggest Fat Cats: * @langfield, may their beatific username go down in online fame. May the poets (so, hip hop artists) sing their virtues before a rapt audience of Bay Area techno-futurologists and ChatGPT-embodied automatons. @langfield generously donated more to @beartype than I thought human(e)ly possible. The worst part is that I never gave @langfield what they wanted: **deep type-checking.** 2024 is the year that @beartype makes good on its promises to @langfield. * @patrick-kidger, the extreme scuba-diving Google X globetrotter and likely *Thunderball* remake stunt double who quietly nominated @beartype for a [**Google Open Source Peer Bonus award**](https://opensource.google/documentation/reference/growing/peer-bonus). Shock twist: @beartype won. This is why I now rep @patrick-kidger merch. [`jaxtyping`](https://github.com/google/jaxtyping) support? We do that. [Equinox](https://github.com/patrick-kidger/equinox) support? That too. If it's a @patrick-kidger byproduct, @beartype probably now hawks it on Etsy. * @KyleKing, one of @beartype's longest-running GitHub sponsors. He wore glorious glasses that sparkle with the fury of a thousand suns. He's also helped *immensely* over the years – both financially and across our issue tracker. May @KyleKing achieve his dreams of delivering AI-fuelled nanobots directly into the bloodstreams of millions of Americans in a well-regulated legislative environment that guarantees safety through the power of federal bureaucracy. Hideo Kojima knows where that road goes... *and it goes somewhere awesome.* @beartype supporters fight for you. Their username is legend. ![](https://media1.tenor.com/m/bCPUaLNx5E0AAAAC/ultra-galaxy-fight-the-destined-crossroad-ultra-galaxy-fight.gif) <sup>@langfield is... *Some Dude in a Skintight Rubber Suite.*</sup> <sup>also featuring @patrick-kidger (left) and @KyleKing (right)</sup> <sup>also featuring all your codebase bugs (kaiju gettin rocked)</sup> ## `pytest-beartype`: It's a Steaming Hot Thing, Now Devtools 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, let `pytest-beartype` confine @beartype to just your `pytest`-based test suite. Who cares if `pytest` burns down, am I right? Anyone? Let's begin: 1. **Install** this steaming hot thing: ```bash pip install --upgrade beartype ``` 2. **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 the `pytest` command, where `--beartype-packages` is a comma-delimited list of all package names to be ~~infested~~ ~~polluted~~ ~~sullied~~ ~~throttled~~ type-checked by @beartype: ```bash pytest --beartype-packages='final_doom,u_wut_mate,strawberry.pancakes' # <-- feels surprisingly good ``` * **Modify** your existing top-level `pyproject.toml` configuration file with a new `[tool.pytest.ini_options]` section resembling: ```toml # In your "pyproject.toml" file... [tool.pytest.ini_options] beartype_packages = 'final_doom,u_wut_mate,strawberry.pancakes' # <-- just. do. it. ``` For those who love CLI warrioring but hate POSIX-compliant shell syntax, <sup>`bash`, u make me hurt inside</sup> also check out @tusharsadhwani's [`zxpy`](https://github.com/tusharsadhwani/zxpy): a Python **+** `bash` mashup that basically throws out the entirety of `bash`. Okay. So, it's more a beatdown than a mashup, really. Bash that `bash` *up*, Python! ![](https://media1.tenor.com/m/dBCaK76vhpkAAAAd/tokusatsu-ultraman.gif) <sup>`pytest-beartype` surveys all it has done. conclusion: "this is fine"</sup> ## 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 ~~good~~ ~~decent~~ acceptable. @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. ![](https://media1.tenor.com/m/BozVxUTt3ZwAAAAd/storium-beam-storium.gif) <sup>@beartype 0.17.0: "Extruded alien protein chunks in *my* dinner?"</sup> ## 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: * **Deep type-checking** of *all* type hints in `O(1)` constant time. * **Deep type-checking** of *some* type hints in `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: > "But does @beartype 0.17.0 actually *do* anything?" *...heh.* Let's begin. ![](https://media1.tenor.com/m/4AuZaaGLYAIAAAAC/ultraman-alligator.gif) <sup>@beartype 0.17.0: "Mutated alien alligators ain't no thang."</sup> ## Beartype 0.17.0: The Interquel We All Deserve @beartype 0.17.0 massively increases the configurability of @beartype. Because everybody always wanted to: * Emit **non-fatal warnings** on type-checking violations? Yup. We got that. Grep `violation_*type`, then win. * Raise **custom exception types** on type-checking violations? We got that, too. `violation_*type` grepping intensifies. * Raise **custom exception messages** on type-checking violations? Got that. `__instancecheck_str()` enters the chat emboldened and swaggering. * Modify the **verbosity** of type-checking violation messages? Got that. `violation_verbosity` + `BeartypeVerbosity` is snickering in the back. * Reduce complex type hints to simple **type aliases** in type-checking violations? That is a thing now. `type {name} = {hard_stuff} | {moar_stuff}`. * Blatantly lie about the types your API expects by instructing @beartype to internally transform source to target type hints matching various patterns with **type hint overrides**? You know we even got that. `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. ![](https://media1.tenor.com/m/zDq3QV45LB8AAAAd/ultra-galaxy-fight-the-destined-crossroad-ultraman.gif) <sup>@felixchenier (right) threatens bugs (offscreen) as @leycec (left) supports</sup> 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](https://beartype.readthedocs.io/en/latest/api_claw/#beartype.claw.beartype_all) 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 configure `beartype.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**: ```python # In your "{your_package}.__init__": from beartype import BeartypeConf from beartype.claw import beartype_this_package, beartype_all beartype_this_package() # <-------------------------------------- raise exceptions for your package beartype_all(conf=BeartypeConf(violation_type=UserWarning)) # <-- emit warnings for everyone else's ``` That's it. That's the Flux Beartyper. This K-k-k-killer Combo compels @beartype to: * **Enforce** type hints across the **current package** by raising fatal exceptions on type-checking violations in this package. * **Complain** about (but do *not* enforce) type hints across all **other packages** and the standard library itself by emitting non-fatal warnings on type-checking violations in those packages. 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. ![](https://media1.tenor.com/m/7p_BMVaF41QAAAAC/ultra-galaxy-fight-the-destined-crossroad-ugf-tdc.gif) <sup>in the endless struggle of bad versus good code, only roundhouse chops to the scaled carapace will decide the fate of your investment portfolio</sup> ## `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, I feel happy about this startling explosion of unmaintainable technical debt and bewildering code complexity. ![](https://media1.tenor.com/m/7ZsiupwO3CEAAAAd/ultraman-kamen-rider.gif) <sup>is that what our lives have come to</sup> ### `violation_*type`: When You Know Better, You Better Tell @beartype By default, @beartype raises thoughtful but eyebrow-raising type-checking violations with exception types like `beartype.roar.BeartypeCallHintParamViolation` and `beartype.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: * **(Recommended)** `violation_type`, the default type of exception raised by @beartype when a type-checking violation occurs – *any* type-checking violation, including: * When an object passed to `die_if_unbearable()` violates a type-check. * When a parameter of a `@beartype`-decorated callable violates a type-check. * When a return of a `@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 such as myself may also enjoy fine-tuning: * `violation_door_type`, the type of exception raised by @beartype when an object passed to `die_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 calling `die_if_unbearable()`, this is also the type of exception raised when an annotated variable violates its type hint. Defaults to `beartype.roar.BeartypeDoorHintViolation`. * `violation_param_type`, the type of exception raised by @beartype when a parameter violates its type hint. Defaults to `beartype.roar.BeartypeCallHintParamViolation`. * `violation_return_type`, the type of exception raised by @beartype when a return violates its type hint. Defaults to `beartype.roar.BeartypeCallHintReturnViolation`. `violation_type` is merely a convenience enabling users to trivially control the ``violation_door_type``, ``violation_param_type``, and ``violation_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: ```python from beartype import BeartypeConf from beartype.claw import beartype_this_package # My spirit guide says that normalcy is a state of mind. Yet, you disagree. beartype_this_package(conf=BeartypeConf( violation_type=AttributeError, # <-- actually, never pass "AttributeError" violation_door_type=RuntimeError, # <-- ..................or "RuntimeError" violation_param_type=TypeError, # <-- Okay. Fine. This is okay. violation_return_type=ValueError, # <-- passing "ValueError": not a great idea )) ``` ![](https://media1.tenor.com/m/7UHSHSVKAYsAAAAC/ultra-galaxy-fight-the-destined-crossroad-ugf-tdc.gif) <sup>does that dude in the back have drills for arms? really? so coll</sup> ### `violation_*type`: When Exceptions Are Too Scary for the Slumber Party Warnings are exceptions in Python. I know, right? Who knew. All these years. The builtin `Warning` class subclasses the builtin `Exception` class. Oddly, this implies that warnings are technically raisable as exceptions. They are, but you shouldn't. Warnings should only be emitted with `warnings.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 a `violation_*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 calls `warnings.warn()` rather than `raise`. 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](https://en.wikipedia.org/wiki/Plane_(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: ```python # In your "your_package.__init__" submodule: from beartype import BeartypeConf from beartype.claw import beartype_this_package # Emit non-fatal warnings on type-checking violations from your own package. # The monolithic codebase that you preserve might just be named `FuglyBugs`. beartype_this_package(conf=BeartypeConf(violation_type=UserWarning)) ``` Of course, you can pass an app-specific `UserWarning` subclass rather than `UserWarning`. And... *you should probably do that.* ![](https://media1.tenor.com/m/Nx_ehaoUlNQAAAAC/ultra-galaxy-fight-the-destined-crossroad-ultra-galaxy-fight.gif) <sup>`FuglyBugs`! spit it! who did this 2 u!?</sup> ## `violation_verbosity` + `BeartypeVerbosity`: Massage Your Brain with Beartype Let 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 new `beartype.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 hand* * `BeartypeViolationVerbosity.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: * A partial diff of the beartype configuration under which this violation occurred. By "partial diff," I mean only the subset of configuration options explicitly set by you that differ from their default values. Previously, violations unnecessarily described *all* options -- even those *not* explicitly set by you that defaulted to their default values. Awful. `violation_verbosity` defaults to `BeartypeViolationVerbosity.DEFAULT`, because your brain is a precious quantity. But you know better. ```python # In your "{your_package}.__init__": from beartype import BeartypeConf, BeartypeVerbosity from beartype.claw import beartype_this_package beartype_this_package(conf=BeartypeConf( violation_verbosity=BeartypeVerbosity.MAXIMAL)) # <-- vomitous output: *ON* ``` ![](https://media1.tenor.com/m/jprsLrCg8eAAAAAd/yapool-ultraman.gif) <sup>never trust a mechanized triceratops named Yapool is all I'm sayin'</sup> ## `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](https://peps.python.org/pep-0484) – the standard named "Type Hints," so that's probably what it's about – included this bizarre substandard <sup>*see wut i did there*</sup> named the [implicit numeric tower](https://peps.python.org/pep-0484/#the-numeric-tower). The idea was simple, albeit horrible. Static type-checkers would just globally replace all: * `float` types in type hints with `float | int` unions. * `complex` types in type hints with `complex | 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* subclass `int`). 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 a `beartype.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 hypothetical `frozendict` builtin), enabling the memoized `BeartypeConf` dataclass to accept dictionaries while preserving caching. And... @beartype globally and recursively substitutes all type hints that are keys of the `hint_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: ```python # Tiresome machinery. Why won't you import yourself, already? from beartype import BeartypeConf, BeartypeHintOverrides # This new beartype configuration explicitly does the same thing as... explicit_numeric_tower = BeartypeConf(hint_overrides=BeartypeHintOverrides({ float: float | int, complex: complex | float | int, }) # This old beartype configuration. implicit_numeric_tower = BeartypeConf(is_pep484_tower=True) ``` 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: ```python # Even more machinery. Just import yourself, already! import numbers from beartype import BeartypeConf, BeartypeHintOverrides # Beartowertype: the ultimate @beartype configuration for numerical analysis. # If you can't count the statistical variance of your lies, neither can I. beartowertype = BeartypeConf(hint_overrides=BeartypeHintOverrides({ int: numbers.Integral, float: numbers.Real, complex: numbers.Complex, }) ``` Users don't understand what `numbers.Integral` means. But users *do* understand what `int` means. Okay. Not all users. A majority of users. Okay. Not even that. A few users understan... Okay. *You* understand what `int` 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 standard `numbers` module, also referred to as the "explicit numeric tower"](https://docs.python.org/3/library/numbers.html). By instructing @beartype to internally replace all builtin scalar types like `int` with corresponding ABCs in the explicit numeric tower like `numbers.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. <sup>*wait, what does that one-liner actually mean*</sup> ![](https://media1.tenor.com/m/6FUnSuzn_g8AAAAC/ultraman-fight.gif) <sup>caring means punching a monster in the gut for humanity</sup> ## 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: ```python # In your "{your_package}.__init__": from beartype import BeartypeConf, BeartypeHintOverrides, BeartypeVerbosity from beartype.claw import beartype_this_package, beartype_all beartype_this_package(conf=BeartypeConf( # <-- This. Is. His. Way. violation_type=TypeError, # <-- what is @beartype to you, a joke? violation_verbosity=BeartypeVerbosity.MINIMAL, # <-- SILENCE, HUMANS! hint_overrides=BeartypeHintOverrides({ # <-- NumPy + SymPy = ancient power int: numbers.Integral, float: numbers.Real, complex: numbers.Complex, }), )) beartype_all(conf=BeartypeConf( violation_type=UserWarning, # <-- unexpected Flux Beartyper returns )) ``` Configure @beartype like Félix Chénier would. The feeling of power is delicious, yet kinda indescribable. ![](https://media1.tenor.com/m/ouHrvz7L1GMAAAAC/ultraman-trigger-new-generation-tiga-ultraman-trigger.gif) <sup>so this is what feels like when monsters cry</sup> ## 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! ```python beartype.roar.BeartypeCallHintParamViolation: @beartyped __main__.f() parameter x="Array(1, dtype=int32, weak_type=True)" violates type hint <class 'jaxtyping.Float[Array, 'dim1']'>, as <protocol "jaxlib.xla_extension.ArrayImpl"> "Array(1, dtype=int32, weak_type=True)" not instance of <class "jaxtyping.Float[Array, 'dim1']">. ``` 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. ![](https://media1.tenor.com/m/eHOAajTpCuAAAAAC/ultra-seven-kaiju.gif) <sup>@beartype: just some rando in a skin-tight rubber suit after all</sup> ### `__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 a `bool`, however, `__instancecheck_str__()` returns a `str`. The full signature resembles: ```python def __instancecheck_str__(cls, obj: Any) -> str: ``` This will soon make sense. Would I lie? This is your final API for raising human-readable violations: ```python from beartype import beartype # Define __instancecheck_str__() on the metaclass. class MetaclassOfMuhClass(type): def __instancecheck_str__(cls, obj: object) -> str: return ( f'{repr(obj)} has disappointed {repr(cls)}... ' f'for the last time.' ) # The actual class is the same as it always was. So boring. class MuhClass(object, metaclass=MetaclassOfMuhClass): pass # Use that class as a type hint everywhere. @beartype def muh_func(muh_obj: MuhClass) -> None: pass # Cheers! You now have all the power while @beartype weeps in the corner. muh_func("Some strings, you just can't reach.") ``` ...which now raises the humane violation: ```python beartype.roar.BeartypeCallHintParamViolation: Function __main__.muh_func() parameter muh_obj="Some strings, you just can't reach." violates type hint <class '__main__.MuhClass'>, as "Some strings, you just can't reach." has disappointed <class '__main__.MuhClass'>... for the last time. ``` Simple, right? And it actually is. Caveats may apply. Notably, the string you return from your `__instancecheck_str__()` implementation: * Should be a sentence fragment whose first character is typically *not* capitalized. * May optionally be suffixed by a trailing period. In either case, @beartype intelligently does the right thing and ensures that the entire violation message is *always* suffixed by only a single trailing period. `__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.* ![](https://media1.tenor.com/m/dKl6k2I2geIAAAAC/ultraman-kaiju.gif) <sup>just another day at the kaiju office ends on a terrifying footnote</sup> ### PEP 695: Only the Third Time Python Standardized Type Aliases @beartype 0.17.0 now *sorta* supports [PEP 695-compliant **type aliases**](https://peps.python.org/pep-0695/#generic-type-alias) (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: ```python # What could be simpler? <-- something nobody should ever say >>> from numpy.typing import ArrayLike >>> ArrayLike typing.Union[ collections.abc.Buffer, numpy._typing._array_like._SupportsArray[numpy.dtype[typing.Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[typing.Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[ typing.Union[bool, int, float, complex, str, bytes]] ] # ...so. literally everything is array-like, huh? even bools, huh? *sigh* ``` Now imagine – in the vast, cavernous, and crawling darkness beyond your eyelids – what happens when you annotate `@beartype`-decorated classes and callables with `numpy.typing.ArrayLike`. Thankfully, you don't even have to imagine: ```python >>> from beartype import beartype >>> from numpy.typing import ArrayLike >>> @beartype ... def run_nurgle_run(like_an_array: ArrayLike) -> None: pass >>> run_nurgle_run(('Blood for the Blood God!', 'Skulls for the Skull Throne!',)) beartype.roar.BeartypeCallHintParamViolation: Function __main__.run_nurgle_run() parameter like_an_array=('Blood for the Blood God!', 'Skulls for the Skull Throne!') violates type hint typing.Union[collections.abc.Buffer, numpy._typing._array_like._SupportsArray[numpy.dtype[typing.Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[typing.Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[typing.Union[bool, int, float, complex, str, bytes]]], as tuple ('Blood for the Blood God!', 'Skulls for the Skull Throne!'): * Not bool, float, str, <protocol ABC "collections.abc.Buffer">, complex, bytes, or int. * Not instance of <protocol "numpy._typing._array_like._SupportsArray">. * Not instance of <protocol "numpy._typing._nested_sequence._NestedSequence">. * Not instance of <protocol "numpy._typing._nested_sequence._NestedSequence">. ``` 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: ```python # Don't try this under Python < 3.12. Just... don't. >>> from beartype import beartype >>> from numpy.typing import ArrayLike as _ArrayLike >>> type ArrayLike = _ArrayLike # <-- tautological nonesense *or* 4d chess move? >>> @beartype ... def run_nurgle_run(like_an_array: ArrayLike) -> None: pass >>> run_nurgle_run(('Blood for the Blood God!', 'Skulls for the Skull Throne!',)) beartype.roar.BeartypeCallHintParamViolation: Function __main__.run_nurgle_run() parameter like_an_array=('Blood for the Blood God!', 'Skulls for the Skull Throne!') violates type hint ArrayLike, as tuple ('Blood for the Blood God!', 'Skulls for the Skull Throne!') not tuple ('Blood for the Blood God!', 'Skulls for the Skull Throne!'): * Not bool, float, str, <protocol ABC "collections.abc.Buffer">, complex, bytes, or int. * Not instance of <protocol "numpy._typing._array_like._SupportsArray">. * Not instance of <protocol "numpy._typing._nested_sequence._NestedSequence">. * Not instance of <protocol "numpy._typing._nested_sequence._NestedSequence">. ``` 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 simply `ArrayLike`. 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! ```python type ChooseYourEpicFate = str | int # <-- this is fine ``` * **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. ```python type UnseenHorror = ClassOfDoom | ClassOf94 # <-- this is sorta fine... # <-- if you "beartype.claw"; # <-- else, defly *NOT FINE*. ``` * **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. ```python def dry_your_tears_on_my_git_stash() -> None: type AyyLmao = LocalAreaMan | UniversalAreaGrey # <-- *NOT FINE* ``` * **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. ```python type FurBaby = TerrorCat | DogBreath | list[FurBaby] # <-- *NOT FINE*... yet ``` 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's `git log`: > The most problematic and troubling aspect of PEP 695 with respect to > runtime type-checking is its horrifying, terrifying, and frankly > shocking **lack of runtime support for forward references.** Although > PEP 695 repeatedly mic-drops forward references as a central motivation > for its existence, the actual runtime implementation of PEP 695 lacks > any support whatsoever for forward references and even goes to elaborate > and outrageous lengths to prevent runtime type-checkers from resolving > forward references in PEP 695-compliant type aliases. Yet again, a new > runtime-hostile PEP arises. Thankfully, this is @beartype. We do what we > want here. And what we want here is to fully dismantle PEP 695, break > it, and then bend it to our perfidious will until this Accursed > Abomination Unto Nuggan does what it was advertised but failed to do at > runtime. So says the Bear. **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 pybearthon ~~walks~~ slithers its own way. In this case, pybearthon silently transforms... ```python # This otherwise broken global type alias... type UnseenHorror = ClassOfDoom | ClassOf94 # ...into this suddenly worky global type alias! from beartype._util.hint.pep.proposal.utilpep695 import ( iter_hint_pep695_forwardref as __iter_hint_pep695_forwardref_beartype__) type UnseenHorror = ClassOfDoom | ClassOf94 for _ in __iter_hint_pep695_forwardref_beartype__({alias_name}): globals()[_.__name_beartype__] = _ # <-- don't ask. srsly. just... don't. ``` Because PEP 695 is fundamentally broken, that same AST transformation fails at local scope with insane exceptions like: ```python NameError: cannot access free variable 'ClassOfDoom' where it is not associated with a value in enclosing scope ``` 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-readable `SyntaxError` exceptions at bytecode generation time on attempting to import *any* module containing even a single `type` alias regardless of where in that module that `type` alias is." This is `True` 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 behind `if` conditionals like `if 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: * Isolate *all* `type` aliases across your entire codebase to a unique submodule conditionally imported only under Python ≥ 3.12. * Conditionally and dynamically declare `type` aliases with the `exec()` builtin hidden behind clever `if` conditionals like `if 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*: ```python # Pretend this means something to you. from numpy.typing import ArrayLike as _ArrayLike from sys import version_info from typing import TYPE_CHECKING, NewType, TypeAlias # If mypy or pyright, pacify mypy or pyright with... deprecated syntax!?!? if TYPE_CHECKING: ArrayLike: TypeAlias = _ArrayLike # Else, we are Python. # # If we are Python ≥ 3.12, dynamically declare a type alias to avoid # "SyntaxError" complaints from older Python interpreters. elif version_info >= (3, 12): exec('type ArrayLike = _ArrayLike') # <-- stupidly clever or just stupid? you decide # Else, we are obsolete Python. In this case, abuse PEP 484 for justice. else: ArrayLike = NewType('ArrayLike', _ArrayLike) ``` 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. ![](https://media1.tenor.com/m/8wrz4mj39UgAAAAd/ultraman-orb.gif) <sup>that feeling when you realize you wasted ten minutes of your life</sup> ## 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`](https://github.com/python/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 the `typeshed`, CPython devs. Can't you literally copy-paste type hints from the `typeshed` 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`, and `mypy` 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: <sup>it's absolutely not simple</sup> 1. `typeshed` devs intend to annotate something in the standard library for which no standard type hints exist. 1. CPython devs quietly augment an existing type to support subscription (i.e., the `__getitem__()` dunder method) *without* documenting anything or standardizing the semantic meaning of the resulting object. 1. `mypy` devs quietly interpret subscription of that type as a non-standard type hint in an obscure and undocumented `mypy`-specific manner. 1. `typeshed` devs annotate things in the standard library using those non-standard type hints in an obscure and undocumented `mypy`-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 standard `os.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 the `mypy` issue tracker for a hot minute. Surprisingly, this is what `os.PathLib[T]` means: ```python from typing import Generic, TypeVar, Union T = TypeVar('T', bound=Union[str, bytes]) class PathLike(Generic[T]): def __fspath__(self) -> T: ... # <-- hoh, boy ``` 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: * *All* `weakref.weakref[T]` type hints to the `weakref.weakref` class. * *All* `os.PathLib[T]` type hints to the `os.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](https://www.youtube.com/watch?v=FGBhQbmPwH8). @beartype 0.17.0: all the bad stuff, now in one easy convenient package. ![](https://media1.tenor.com/m/0eMvsNw1S1cAAAAC/ultraman-trigger-new-generation-tiga-ultraman-trigger.gif) <sup>only a flying water bottle on fire holds the key to planetary salvation</sup> ## Isomorphic Non-closure Wrapper: Changelog from Hell Continues *ohmygodswontthischangelogquitalready* Apparently, this is fine now: ```python from beartype import beartype from functools import wraps def muh_func(muh_arg: int): # <-- wat!? no @beartype!? but how can this be? pass @beartype # <-- oh, okay. here's the @beartype. phew. that was close @wraps(f) # <-- standard decorator idiom def muh_wrapper(*args, **kwargs): pass ``` When a changelog just needs to stop already, animated Ultraman GIF. ![](https://media1.tenor.com/m/k1wByAC3R74AAAAC/ultra-galaxy-fight-the-destined-crossroad-ultraman.gif) <sup>Cobra Guy encourages at-risk codebases to try a bit harder</sup> ## @patrick-kidger: If He Didn't Type It, You Shouldn't Use It Lastly, @beartype 0.17.0 officially: * Supports @patrick-kidger's [Equinox](https://github.com/patrick-kidger/equinox) now. JAX + ML + @beartype = *pretty sure you just got a raise.* * Does *not* support [`nptyping`](https://github.com/ramonhagenaars/nptyping), because **(A)** `nptyping` is dead and **(B)** `nptyping` is bad. Dead and bad are both bad. If you attempt to use `nptyping`-based type hints under @beartype ≥ 0.17.0, @beartype will **raise fatal exceptions** informing you that you are bad by association: e.g., ```python beartype.roar.BeartypeDecorHintPep604Exception: Type hint NDArray[Shape['N, N'], Float] inconsistent with respect to repr() strings. Since @beartype requires consistency between type hints and repr() strings, this hint is unsupported by @beartype. Consider reporting this issue to the third-party developer implementing this hint: e.g., >>> repr(NDArray[Shape['N, N'], Float]) NDArray[Shape['N, N'], Float] # <-- this is fine >>> repr(NDArray[Shape['N, N'], Float] | int) nptyping.ndarray.NDArray | int # <-- *THIS IS REALLY SUPER BAD* # Ideally, that output should instead resemble: >>> repr(NDArray[Shape['N, N'], Float] | int) NDArray[Shape['N, N'], Float] | int # <-- what @beartype wants! ``` 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 import `nptyping`. *Not even once.* Thankfully, @patrick-kidger exists. <sup>hard to prove, but likely true</sup> Everybody wants to transition from `nptyping` to @patrick-kidger's [Google-adjacent `jaxtyping` package](https://github.com/google/jaxtyping) already. Unlike `nptyping`, `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](https://beartype.readthedocs.io/en/latest/faq/#numpy-arrays). The data science pipeline you save might just be your own. It's probably too late, though. ![](https://media1.tenor.com/m/q7TrSkO7jMQAAAAd/ultraman-1966.gif) <sup>your codebase when you realize you used `nptyping` everywhere</sup> ## Biosecurity Tickets: No Longer Something I Grovel on the Floor For Previously, I publicly begged for [GitHub Sponsors](https://github.com/sponsors/leycec) 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. <sup>*awkward collar tugging*</sup> 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](https://github.com/sponsors/leycec), 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: > "What next? How do we positively incentivize open-source volunteerism > without disproportionately burdening the individuals that already bear > up too much?" 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. ![](https://media1.tenor.com/m/fvq9y6sTwmIAAAAd/ultra-galaxy-fight-the-destined-crossroad-ultraman.gif) <sup>@beartype userbase: make a better world through glowing orbs</sup> ## 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. ![](https://media1.tenor.com/m/t4NwisgU--kAAAAd/fake-ultraman-agul-smile.gif) <sup>@beartype feels that feeling, too</sup>
- Loading branch information