Skip to content

Conversation

renovate[bot]
Copy link

@renovate renovate bot commented Mar 5, 2025

This PR contains the following updates:

Package Type Update Change Age Confidence
beartype (source) minor >=0.19.0,<0.20.0 -> >=0.22.2,<0.23.0 age confidence
beartype (changelog) project.dependencies minor >=0.19.0,<0.20.0 -> >=0.22.2,<0.23.0 age confidence

Warning

Some dependencies could not be looked up. Check the Dependency Dashboard for more information.


Release Notes

beartype/beartype (beartype)

v0.22.2: Beartype 0.22.2: Yo Dawg We Heard You Like LLM

@​beartype is proud as a cub gnawing its first salmon to announce: @​beartype has been Tidelifted! For our security-conscious corporate and government userbase, the best way to secure your enterprise and support @​beartype is now through Tidelift vis-a-vis a SonarQube Advanced Security subscription. More on that later. We now return to your regularly scheduled release party. DJ Leycec in residence. Hit those fat QA jams.

Beartype 0.22.0 0.22.1 0.22.2 portals into the mortal plenum with a disturbing "WHOOOMP!" As you panic, all the oxygen in the room is rapidly vacuumed into an adjacent hyperdimension. It's not @​beartype's safest entrance – but it's one we're all sure to remember. This is @​beartype 0.22.2: don't ask what happened to 0.22.0. just... don't.

pip install --upgrade --pre beartype   # beartype casts magic missile on the darkness

The central dogma of @​beartype 0.22.2 is LLM compatibility. Do you like LLM? Do you like compatibility? Then your code likes @​beartype 0.22.2 (even against your better judgement). But before the liking starts...

@​beartype 0.22.2 salutes you who are about to code

@​leycec and his beautiful science wife are eating well. Thanks entirely to...

GitHub Sponsors: Befriend the Bear and Get a Bear for Life

This release comes courtesy these proud GitHub Sponsors, without whom @​leycec's cats would currently be eating grasshoppers in the abandoned back lot again:

Additional financial shout-outs to @​ilyapoz (@​Ilia Pozhilov), the amazing former Yandex code cosmonaut who graciously donated a pile of Georgian lari to @​beartype this go-around. Apparently, the lari is denominated in the ლ Unicode character. What a symbol! It looks like a beautiful hat. If only the Canadian dollar was half as manly. 😭

Thanks so much, masters of fintech and Yandex.

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

Tidelift: A Rising Tide Lifted @​beartype's Not-At-All-Leaky Boat

This release also comes courtesy Tidelift, which very graciously pays out recurring income to security-sensitive open-source projects like @​beartype, NumPy, and other stuff you probably care about. @​beartype joining Tidelift has super-positive implications for Python's broader QA community – including:

  • No rugpull. @​beartype is much less likely to die, disappear, or otherwise wither on the entropic vine now. Sadly, most open-source software does. Without recurring income, most open-source software never makes it to the Tidelift stage. It vanishes without a trace at 5:37AM on a Monday morning, leaving your once-profitable corporate enterprise and your once-stable local government without a working QA solution. That won't happen to @​beartype.
  • Actual security. Thanks to Tidelift making me do it, @​beartype now features an actual honest-to-Ursula security policy. It's boring. It's bog-standard. If we were responsible, we'd encourage you to read it. Instead, here's the tl;dr:
    1. @​beartype users who discover security vulnerabilities are invited to privately disclose those vulnerabilities by submitting a GitHub-managed security vulnerability.
    2. @​beartype will then privately resolve those vulnerabilities.
    3. @​beartype will then publish a new stable release containing those resolutions.
    4. @​beartype will then publicly document your discovery, crediting you and your fearsome l33t skills. Seriously, they're fearsome. We're afraid. With great power comes great responsibility QA. Somebody smart said that.
    5. GitHub will then include these vulnerabilities in its own GitHub Advisory Database, improving everyone's security. Luckily, you are part of everyone. ...what does that even mean?

If you represent a security-conscious corporate, government, or non-profit, the best way bar none for you to support @​beartype and secure your own workflow is by subscribing to Tidelift through SonarQube Advanced Security. Security giant Sonar recently acquired Tidelift, guaranteeing the economic viability of the Tidelift model for billions of future open-source projects that have yet to be born. Join the jargon-laden conversation and pay someone else to think about unreadable acronyms like SAST, SCA, and SBOM for once.

Pictured: Artistic rendition of the @​beartype development process before Tidelift. You weren't supposed to see this.

!!STUFF YOU WANNA READ EVEN THOUGH ITS EXHAUSTING!!

You really want to bump the @​beartype requirement in your pyproject.toml file. Preserve end user sanity tomorrow by explicitly requiring a minimum of @​beartype >=0.22.2 today: e.g.,

##### In your top-level "pyproject.toml" configuration:
dependencies = [
    ...
    "beartype >=0.22.2",  # <-- once you go 0.22.2, you go 0.22.2 for life
    ...
]

Why? Bear with us. This explanation may bore you. Imagine what typing this out felt like.

Prior versions of @​beartype are fundamentally incompatible with Python 3.14. Python packagers like pip and uv do not update packages by default. Unless you bump your @​beartype requirement, your existing userbase who already installed your package will be unable to use your package under Python 3.14 – even after manually updating your package, because manually updating your package fails to transitively update all dependencies of your package by default. In other words, Python packaging still kinda sucks.

This is why...

@​beartype looks stoically into the wind as everything breaks

!!MORE STUFF YOU WANNA READ EVEN THOUGH ITS EXHAUSTING!!

beartype.claw import hooks have been supremely revamped. They still work the same, but they're now explicitly compatible with a lot more than they used to be. If you tried enabling beartype_this_package() but switched back to manually decorating everything with @beartype because beartype_this_package() spewed too many errors everywhere, please give beartype_this_package() a second chance:

##### In the "{your_package}.__init__" submodule:
from beartype.claw import beartype_this_package
beartype_this_package()  # <-- we're willing to swear on our pinkies that this works now

Let us know how it goes. If stuff is still busted, we'll immediately fix that stuff. We now have the necessary machinery in our abstract syntax tree (AST) transformer to support all (or most, anyway) compatibility woes previously associated with beartype.claw import hooks.

the new beartype.claw shamelessly dances for anyone

tl;dr: Llamas, Bears, & You

@​beartype 0.22.2 dramatically improves compatibility across the board with the APIs, packages, and paradigms you care about. Because you care, we care. In fact, we spent whole months caring. We cared so much we're all cared out. The summer went away in a blitz of caring and we didn't even notice. Oh, Gods. The warmth has fled. The snow is coming. And all we have to show for it is massive compatibility gains across the board.

This includes:

  • Python 3.14! 🐍
  • LangChain! ⛓️
  • FastMCP! 💨
  • Celery! 🥗
  • Polars + Pandera! 🐻‍❄️
  • PEP 646! It doesn't really fit the LLM theme, but we did it anyway! It's turkey time. 🦃

@​beartype 0.22.2 weeps from pride and accomplishment, but mostly sleep deprivation

Python 3.14: Oh, It's Big

Python 3.14. You care about Python 3.14 even if you don't know you care. @​beartype now fully supports PEP 649 and 749 – landmark QA standards introduced by Python 3.14 that finally obsolete PEP 563. All these numbers mean something. We swear. Since all horror stories start with two nondescript teens in a Buick, let's start there:

from __future__ import annotations

That's PEP 563. And... that's now deprecated. PEP 749 officially deprecated PEP 563 a few months ago:

Sometime after the last release that did not support PEP 649 semantics (expected to be 3.13) reaches its end-of-life, from __future__ import annotations is deprecated. Compiling any code that uses the future import will emit a DeprecationWarning. This will happen no sooner than the first release after Python 3.13 reaches its end-of-life, but the community may decide to wait longer.
After at least two releases, the future import is removed, and annotations are always evaluated as per PEP 649. Code that continues to use the future import will raise a SyntaxError, similar to any other undefined future import.

tl;dr: On October ~15th 2029, from __future__ import annotations will be officially deprecated. On October ~15th 2031, from __future__ import annotations will be removed entirely from the Python language. At that time, any module using from __future__ import annotations will raise a SyntaxError at importation time and thus become unimportable. In 2025, nobody should enable from __future__ import annotations voluntarily.

Sometime over the next several five years, you and your righteous dev team will require Python ≥ 3.14 as a mandatory dependency. That's just the way of the Python world. Planned obsolesce is the road we walk. When that happens, you'll no longer need from __future__ import annotations to declare forward references to undefined types in type hints like this:

##### Look, Ma! No "from __future__ import annotations".
##### We don't need the future where we're going.
from beartype import beartype

##### Under Python ≥ 3.14, this just works. You annotated a function as accepting
##### an instance of a type you haven't even declared yet. Yet, this is now fine.

##### @&#8203;beartype accepts you and your suspicious code for you who are.
@&#8203;beartype
def useless_func(forward_reference_to_undefined_type: ThisJustWorksNow) -> ThisJustWorksNow:
    return forward_reference_to_undefined_type

##### Of course, you *DO* have to eventually define the undefined type used above.
##### If you don't, everything will still blow up. It's not @&#8203;beartype's fault.

##### Python 3.14 made us do it... We blame Guido.
class ThisJustWorksNow(object):
    pass

Unquoted forward references are thus baked into Python 3.14. Order of type hints and types is no longer significant. Define and annotate stuff in any order you like.

@​beartype 0.22.2: because life is too short and code is too long.

@​beartype: always ready to rip its shirt off at the slightest provocation

Large Language Models: Apparently, They're Large

@​beartype 0.22.2 now ships with out-of-the-box support for Bad Boy LLM APIs that defy community standards, mental health, and your last hair follicles. This includes:

  • LangChain, the 500lb gorilla of the LLM space. We all know it. Some of us shake our fist at it. Many of us prostrate ourselves before it in mute supplication. But all of us must admit... LangChain changed the world. Even if everybody now tries to pretend that only LangGraph exists, LangChain still exists underneath it all – lurking in the grimy shadows with a sinister leer plastered across its placid facade. 🧟
  • FastMCP, the LangChain of the Model Context Protocol (MCP) world. None of this makes sense. Even I no longer know what I'm talking about. Just pretend you too know what the increasingly nonsensical alphabet soup of AI-ML-LLM hype train jargon means. Everyone, let us fake it until our resume says we make it. 🚆
  • Celery. Yeah, yeah. Technically, Celery is larger than AI life. It's the Pythonic task queue on massively parallelized compute nodes. Pragmatically, though, even Celery has rapidly pivoted its previously Django-centric worldview towards the sweeter and more fulfilling world of AI hype. I just wish they'd chosen a more delicious vegetable as their nom de plume. I mean... celery? That stuff's disgusting – and I'm a vegan, sometimes occasionally even by choice. 🥗

These APIs are awesome, because they changed the world. But they're also hostile to literally every other Python decorator in existence. @​beartype is a Python decorator in existence. Thus, these APIs are hostile to @​beartype.

Why? Because they all define decorator-hostile decorators: that is, decorators that prevent other decorators from being applied. That's not how decorators are supposed to work. You're supposed to be able to apply decorators in any arbitrary order. That was the deal, LLM APIs! Apparently, LLM APIs hated that deal. Their decorators destructively transform your normal functions and methods (which are decoratable by @​beartype) into abnormal instances of API-specific types (which are not decoratable by @​beartype).

In the worst case of both LangChain and FastMCP, these abnormal instances of API-specific types aren't even callable! They don't just destroy the types of what they decorate. They destroy the callability of what they decorate. What kind of compatibility-hostile API turns a function or method into an uncallable object that you can't do anything with anymore? LangChain and FastMCP. That's who.

Examples include:

  • The @langchain_core.runnables.chain decorator function.
  • The @fastmcp.FastMCP.tool decorator method.
  • The @celery.Celery.task decorator method.

@​beartype 0.22.2 now officially supports decorator-hostile decorators like this. It cost us our summer, but no price is too high. Okay. The price was pretty high. But this is the AI-ML-LLM hype train we're talking about. You either board that train or you prepare to eat everybody else's dust as they point fingers and cruelly guffaw into their monocles at you. @​beartype prefers to board that train.

@​beartype walks its own road, though. @​beartype boards that train in its own special way. Specifically, @​beartype now maintains two internal databases efficiently structured as trie prefix search trees:

  1. The decorator beforelist. The beforelist is @​beartype's first line of LLM defense. It's an internal database of the names of all third-party packages, modules, and types transitively defining one or more decorator-hostile decorators. beartype.claw import hooks then:
    1. Track both your importation of these problematic attributes and your instantiation of these problematic types into your modules.
    2. Inject the @beartype decorator before (rather than after) the last decorator-hostile decorator in existing chains of two or more consecutive decorators decorating your callables and types in your modules.
  2. The decorator blacklist. The blacklist is @​beartype's last line of LLM defense. It's an internal database of the names of all third-party types produced by decorator-hostile decorators in the beforelist. The @beartype decorator now silently ignores attempts to decorate instances of these problematic types. Don't even bother! They're busted, @​beartype. At least we no longer are. 😅

It's... complicated. Really, really complicated. There's a doctoral PhD thesis somewhere here for somebody who wants one. Me? I just wanna sleep. Blessed sleep! Take me now!

left: @​beartype. right: langchain, fastmcp, and celery. you think we forgive you that easily!?

Polars: Cold as Ice, DataFrames Are Nice

Polars is the vibrant Pandas alternative everyone loves. 35k GitHub stars cannot be wrong. Although I feel jealousy when I hear numbers like that, @​beartype now officially supports validation of Polars DataFrames vis-a-vis Pandera – the only sane way to validate the integrity of your fragile DataFrames that are probably falling over as we speak. Poor guys.

Previously, @​beartype only supported validation of Pandas DataFrames via Pandera. Now, @​beartype supports both Pandas and Polars. This can only mean one thing. Our powers are growing at an exponential rate. The QA Singularity is upon us. Check out this terrifying exhibition of runtime validation run amok:

import pandera.polars as papol
import beartype
import pandera.typing.polars as pt
import polars as pl

class Schema(papol.DataFrameModel):
    state: str
    city: str
    price: int = papol.Field(in_range={"min_value": 5, "max_value": 20})

@&#8203;papol.check_types
@&#8203;beartype.beartype
def test_schema(df: pl.DataFrame) -> pt.DataFrame[Schema]:
    return df

df = pl.DataFrame({
    "state": ["Ohio", "Ohio", "Ohio", "Nevada", "Nevada"],
    "city": ["Youngstown", "Akron", "Columbus", "Reno", "Las Vegas"],
    "price": [10, 20, 15, 14, 4]

##### ^-- *VIOLATION*. so bad. so very bad. we're panicking. game over, man!
})
test_schema(df)

...which helpfully blows up with a human-readable exception that relieves you:

Traceback (most recent call last):
  File "/home/leycec/tmp/mopy.py", line 24, in <module>
    test_schema(df)
    ~~~~~~~~~~~^^^^
  File "/home/leycec/py/pyenv/versions/3.13.2/lib/python3.13/site-packages/pandera/decorators.py", line 841, in _wrapper
    return _check_arg("return", out)
  File "/home/leycec/py/pyenv/versions/3.13.2/lib/python3.13/site-packages/pandera/decorators.py", line 722, in _check_arg
    raise error_handler.schema_errors[0]
pandera.errors.SchemaError: error in check_types decorator of function 'test_schema': 
Column 'price' failed validator number 0: <Check in_range: in_range(5, 20)> failure case 
examples: [{'price': 4}]

@​beartype: Bleeding-edge APIs named after magnetic dipoles? Yeah. We do that, too.

@​beartype 0.22.2 pours one out for the DataFrame that didn't make it.

PEP 646: We Don't Understand Variadic Generics Either, But Apparently @​beartype Now Supports Them

@​beartype 0.22.2 fully supports PEP 646 – Variadic Generics. ...for certain glib definitions of "fully supports."

PEP 646 is outrageously huge. It's the longest and cruelest typing standard published to date. Synopsizing that standard is a fool's errand. Thus, we now synopsize that standard. PEP 646 brings two things to the QA table. Let's go! :hurtrealbad:

I-I-Is that @​beartype's arm she's holding up!? 😮

Type Variable Tuples: They're Weird and Kinda Suck, But Maybe Somebody Who Is a Good Person Loves Them

PEP 646 introduces type variable tuples (i.e., typing.TypeVarTuple(...) objects). Whereas good ol' PEP 484-compliant type variables (i.e., typing.TypeVar(...) objects) match only a single type (like MuhGeneric[int] for class MuhGeneric[T](): ...), type variable tuples greedily match zero or more types (like WoahGeneric[int, str, bool] for class WoahGeneric[*Ts](): ...).

The use case is tensors, supposedly. I personally press "F" to doubt that any real-world tensor frameworks will actually leverage type variable tuples. Doing so would inhibit forward compatibility with future tensor APIs in those frameworks. Why? Because type variable tuples match types greedily. Once you've exposed a type variable tuple to your end users through a public-facing generic type, you can never add any other type variables to that generic type without breaking backward compatibility. You're now permanently locked in to that generic type API for the rest of all time.

Aren't ordinary type variables like that too, though? No. You can always add additional type variables to normal generic types without breaking backward compatibility. How? PEP 696-compliant type variable defaults (e.g., T = TypeVar("T", default=int)). Press "F" if anyone would like me to babble incoherently more about all this.

That said, type variable tuples probably do have practical internal use. I'd never expose them to end users for the aforementioned reasons. They cause compatibility woes. Still, @​beartype 0.22.2 now fully supports them. Use them! Reassure us that we didn't waste the entire summer on this! Oh, Gods... the summer... it's... it's gone, everybody. 😩

@​beartype 0.22.2 when it realized where the summer went

Tuple Type Hint Unpacking: They're Also Weird, But They're Actually Useful!?

PEP 646 also introduces tuple type hint unpacking (e.g., tuple[int, *tuple[str, ...]]). This spec goes really hard really fast. The syntax is also kinda nasty. But the core idea is that you can now deeply type-check the contents of non-trivial tuple data structures. You may now be thinking:

"Uhh... wat? Tuple data structures? Can you even use tuples like data structures? Even if you can, should you? Wouldn't a tuple data structure promote unreadable and obfuscated code that not even the unpaid intern wants to maintain?"

Indeed! It's all true! You can use tuples like data structures. @​beartype internally does this all the time. Why? Unjustifiable microoptimizations. Because CPython is highly internally optimized for tuple instantiation, access, and garbage-collection, tuples make the ideal read-only data structures for many perfidious purposes. CPython cares a lot about tuples. Tuples are the backbone of CPython's calling convention, because functions and methods returning multiple values implicitly return tuples of those values. This makes tuples the ideal data structures for implementing many pure-Python algorithms – especially recursive algorithms implemented iteratively.

Of course, tuple data structures do promote unreadable and obfuscated code that not even the unpaid intern wants to maintain. But that's not a problem if you're a dodgy codebase like @​beartype. Ain't nobody maintaining this code except @​leycec – a well-known glutton for punishment-by-dev-hell.

If you are like @​leycec, you too can now type-check your shamefully growing heap of tuple data structures from Hell. How? PEP 646, yo. Previously, PEP 484 only let you type-check two simplistic kinds of tuple hints:

  • Fixed tuple hints match tuples containing a fixed number of items, each matched by a different child type hint. tuple[str, int], for example, is the fixed tuple hint matching tuples containing one string item followed by an integer item.
  • Variadic tuple hints match tuples containing zero or more items all matched by the same child type hint. tuple[str, ...], for example, is the variadic tuple hint matching tuples containing zero or more string items.

That's great if your tuple data structures are simplistic. But they're probably not, are they? That's why you're still reading this at 4:17AM. Your wife is calling out for you in her fitful sleep, but you don't even care! This is riveting Internet reading right here!

So what if your tuple data structures are as complicated as your marriage? You reach for PEP 646 and @​beartype 0.22.2. You can now unpack at most one child tuple hint inside another parent tuple hint. For reasons that will become clear shortly, most child tuple hints unpacked in this way are variadic. Do that and you've now defined a mixed fixed-variadic tuple hint that matches tuples containing (in order):

  1. A fixed leading number of items, each matched by a different child type hint.
  2. Zero or more middle number of items, each matched by the same child type hint.
  3. Either:
    • A fixed trailing number of items, each matched by a different child type hint.
    • Zero or more trailing number of items, each matched by the same child type hint.

tuple[str, bytes, *tuple[int, ...], bool, float], for example, is the mixed fixed-variadic tuple hint matching tuples containing (in order) a string item, a byte string item, zero or more integer items, a boolean item, and a floating-point number item. Cool, right? But the coolness doesn't stop there. Consider cardinality. Old-school variadic tuple hints (like tuple[str, ...]) only match tuples containing zero or more items. But what if you want to match a positive number of items? Tuple hint unpacking trivially lets you do that, too.

tuple[str, *tuple[str, ...]], for example, is the mixed fixed-variadic tuple hint matching tuples containing one or more strings. This example then generalizes to arbitrary cardinality bound only by your patience and openness to syntactic barbarism. Want to match tuples containing a larger number of strings? Just keep prepending str child type hints until you're satisfied, exhausted, and dripping with sweat at 4:17AM.

tuple[str, str, str, str, str, *tuple[str, ...]], for example, is the mixed fixed-variadic tuple hint matching tuples containing five or more strings. It's silly. Yet, it works. Kinda.

@​beartype 0.22.2: it's only silly if you admit it's silly.

beartype 0.22.2 proudly unpacks tuple hints as the wind blows its hair around

Lastly but Beastly (but not Leastly)...

we doin' this

...to financially feed @​leycec and his friendly @​beartype through either:

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

Bear Club: The First Rule of Bear Club Is You Crush Bugs

@​beartype high-fives the reclusive secret society of worldwide bear bros who might possibly care about this. You are the select few. The elect enlightened. You are:

@​posita, @​wesselb, @​tusharsadhwani, @​JWCS, @​patrick-kidger, @​EtaoinWu, @​iamrecursion, @​Moosems, @​langfield, @​sylvorg, @​Glinte, @​cclauss, @​sean-roelofs-ai, @​kultura-luke, @​jonathanberthias, @​gotmax23, @​adamtheturtle, @​ilyapoz, @​MilesCranmer, @​rg936672, @​ddorian, @​k4ml, @​riesentoaster, @​LeonHilf, @​jeertmans, @​mzealey, @​thetianshuhuang, @​RomainBrault, @​alisaifee, @​ArneBachmannDLR, @​JelleZijlstra, @​tactile-metrology, @​RobPasMue, @​GithubCamouflaged, @​kloczek, @​uriyasama, @​danielgafni, @​JWCS, @​rbroderi, @​AlanCoding, @​tvdboom, @​crypdick, @​jvesely, @​komodovaran, @​kaparoo, @​MaximilienLC, @​fleimgruber, @​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, @​minmax, @​jedie, @​pablovela5620, @​thiswillbeyourgithub, @​Logan-Pageler, @​knyazer, @​ilyapoz, @​yuzhichang, @​Fedezzab, @​antonioan, @​im-Kitsch, @​mthramann, @​fbartolic, @​rgallardone, @​frrad, @​jonnyhyman, @​jennydaman, @​likewei92, @​acec2127, @​rudimichal, @​woutdenolf, @​PauloHMTeixeira.

left: @​beartype. right: you. why is it always like this?

v0.21.0: Beartype 0.21.0: Curses, It's Recursion!

Beartype 0.21.0 consoles your codebase as it shudders under the oppressive tidal wave of bugs. Much like its predecessors, @​beartype 0.21.0 is here to help. Unlike its predecessors, @​beartype 0.21.0 claims it solves more problems than it creates for once. Is @​beartype 0.21.0.... lying!? 🫢

pip install --upgrade beartype     # <-- blast all bugs into the git pit

Let your test suite show the truth – even if @​leycec just wants to play obscure French video games with titles like Clair Obscur: Expedition 33 (Because This Couldn't Be More Pretentiously Pseudorandom) the entire weekend and pretend our issue tracker isn't collapsing under its own ponderous weight:

@​beartype 0.21.0: this can't be what you've waited months for

@​beartype 0.21.0 is gratefully 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.

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

Let's get this pawful party started.

tl;dr: Explosive Recursion Never Felt So Good

@​beartype 0.21.0 is obsessed with recursive data structures. They're more common than you might think! Okay. They're totally rare. We all learn about recursive data structures as poverty-stricken undergrads who subsist on years-old cup ramen and then pretend we never learned about them. You'll never need to implement a recursive data structure in pure-Python, because somebody else already did that for you. Graphs, heaps, queues, linked lists, skip lists, trees, and (our personal favourite) tries are all sufficiently awesome that you're already using most of them... because somebody else made them. That's why you're using them! Right? Ain't nobody got spare time or brain space to hack out a pure-Python red-black binary tree in 2025. But somebody did.

@​beartype 0.21.0 is for that somebody. When you need recursion, you need @​beartype 0.21.0.

@​beartype 0.21.0 also acknowledges that 2025 is Humanity on Hard Mode™. The planet isn't doing well. Humanity isn't doing well. Industrial civilization isn't doing well. The US isn't doing well. Even Canada's looking a bit shaky – and we face literal death just by going outside six months of the year. Let's not even mention the deer flies, black flies, mosquitos, ticks, or rabid raccoons. Gods. Anything but the rabid raccoons. Therefore, wherever you are, whatever you face, whenever the darkness erupts and starts gnawing on your codebase...

@​beartype 0.21.0 will be there. We got your codebase's back. In fact, we're currently scratching that back. Feels good, right? These paws have claws – but only for bugs. Your code got lucky.

@​beartype 0.21.0: a familiar face you can trust

Synopsis: When You're So Verbose Even Your Synopsis Is a White Paper

Let @​beartype assuage, massage, and presage ...wat? it's my release party and i'll rhyme if i wanna those issues away. @​beartype 0.21.0 promises it delivers first-class best-of-breed hyphenated-jargon-hype-train support for:

  • Recursive type hints! That's right. Now you too can revel in the disgusting power of infinitely deep data structures with [PEP 695][]-compliant recursive type aliases. The only catch? This unworldly magic needs Python ≥ 3.12, which is quite the catch indeed. Behold! Unworldly magic:

Type hint matching an infinitely recursive list. Look, I don't know. This is for
the extreme sports coders that like to live dangerously and code even harder.

type RecursiveListExplodesYourApp = list[RecursiveListExplodesYourApp]


* **Opt-in dataclass field checking!** That's right. Now you too can type-check `@dataclass` fields on assignment by enabling `is_pep557_fields=True` – much to the dismay of everybody else in the office. The only catch? Actually, there are multiple catches. No relative forward references and no [PEP 563][] support means no `from __future__ import annotations`. That's why dataclass field checking remains disabled by default. You have to opt in, because the best things in life are dangerous and reckless and hurt a lot. Like, *a lot* a lot:

```python

##### @&#8203;beartype: It might not be Pydantic, but at least it cost you nothing.
beartype_this_package(conf=BeartypeConf(is_pep557_fields=True))  # <-- magical explosions?
  • Generalized hint overrides! That's right. Now you too can replace all list[str] type hints with... uhh, list[str] | tuple[str, ...]. Pretend somebody wants this:

Users can now pass tuples of strings to all callables annotated as
accepting only lists of strings. waaaaaaaaaaaaaaaaaaaaaaaaaaaaaaat?

beartype_this_package(conf=BeartypeConf(hint_overrides=FrozenDict({
list[str]: list[str] | tuple[str, ...]}))) # <-- pretend this makes sense


* **Frozen dictionaries!** It's happening, because `beartype.FrozenDict` is making it happen:

```python
from beartype import FrozenDict

##### Finally, a set of frozen dictionaries! Yes, it's all true. Now you too can use
##### dictionaries as dictionaries keys or set members. Why? Because you can.
freezing_my_dict_off = {
    FrozenDict({'My ganglia!': 'It hurts.'}),
    FrozenDict({'What even is a ganglia?': 'No idea. But it surely hurts.'}),
}
  • Probably other stuff! But nobody cares, because nobody even read this far. WAIT. You're reading this far. Clearly, you're somebody – somebody awesome who actually has hair and is profoundly changing the world! It only goes to show you can't believe anything you read in a changelog anymore. 2025: "So even the changelogs lie now, huh?"

@​beartype 0.21.0: If you don't feel like a wild animal while coding, can it be called coding?

Recursion: Still Destroying Lives after All These Years

@​beartype 0.21.0 now officially supports all possible forms of recursion in type hints. This includes directly recursive [PEP 695][] type aliases, indirectly recursive [PEP 484][] generics, and @​beartype-specific hint overrides. Which you prefer depends on which bitter pill you're willing to swallow:

  • If you're willing to require Python ≥ 3.12 as a mandatory dependency, prefer [PEP 695][] type aliases. They're concise. They're descriptive. They're elegant. They "just work" intuitively in the exact way you expect them to:

Annotate recursive data structures with a "simple" one-liner. \o/

type RecursiveListExplodesYourApp = list[RecursiveListExplodesYourApp]


* If you're unwilling to require Python ≥ 3.12 as a mandatory dependency, fallback to [PEP 484][] self-subscripted generics. They're unconcise. They're non-descriptive. They're inelegant. They require heavy lifting on your part before they start working. But they *do* work under Python ≥ 3.9, which is more than we can say for [PEP 695][]:

```python

##### One line that makes sense (above) or five lines that don't make sense (below)?
##### Let the cat decide. ¯\_(ツ)_/¯
from typing import TypeVar
T = TypeVar('T')
class GenericList(list[T]):
    pass
GenericRecursiveListExplodesYourApp = GenericList[GenericList]

Let's take this one recursive app destroyer at a time.

@​beartype 0.21.0: this is the biggest animated gif i have ever seen

Direct Recursion via PEP 695 Type Aliases: Because How Much More Broken Could Your App Get?

Directly recursive [PEP 695][] type aliases is what everybody who wants recursive type hints wants. Against all odds, you're actually reading this. You want recursive type hints. Thus, you want:

##### Type hint matching an infinitely recursive list. Look, I don't know. This is for
##### the extreme sports coders that like to live dangerously and code even harder.
type RecursiveListExplodesYourApp = list[RecursiveListExplodesYourApp]

##### @&#8203;beartype now type-checks this infinitely recursive list in O(1) time. Of
##### course, that's impossible. But @&#8203;beartype doesn't even care anymore! F- it!
from beartype import beartype
@&#8203;beartype
def dangerous_func_means_well(oh_gods: RecursiveListExplodesYourApp) -> None:
  '''
  Dangerous function iteratively recurses into the passed infinitely recursive
  list until either bottoming out at the same list... *or blowing up.*

  Which do you hope happens first?
  '''

##### The growing terror you feel just speed-reading this code is real.
  seen_horror_ids: set[int] = set()
  unseen_horrors: list[RecursiveListExplodesYourApp] = list((oh_gods,))

##### Recurse into that terror. Recurse until your face is numb, like mine.
  while unseen_horrors:

##### Pop that terror like a meat balloon. If it squishes, you must pop it.
      seen_horror = unseen_horrors.pop()
      print(f'Visiting infinitely recursive list: {id(seen_horror)}')

##### Awaken from this logic nightmare, gentle reader!
      if id(seen_horror) in seen_horror_ids:
          print("Recursion detected! We're outta here, suckers!")
          break

##### Pretend this does what the docstring says this does. *gulp*
      seen_horror_ids.add(id(seen_horror))
      unseen_horrors.append(seen_horror[0])

##### Infinitely recursive list. If you try this in the office, you may regret the
##### fateful day your career decisions plummeted down a cliff.
OH_GODS = []
OH_GODS.append(OH_GODS)

##### Pass this function valid input. Stare down the infinitely recursive list?
##### Don't mind if I do! Gape in awe before infinity blows up your call stack.
dangerous_func_means_well(OH_GODS)

##### Pass this function invalid input. No! No! Gods! NOOOOOOOOOOOOOOOOOOOOOOO!
dangerous_func_means_well(['Open-source', 'sells,' 'but', "who's", 'buying?'])

...which raises the expected output and exception traceback:

Visiting infinitely recursive list: 133179499204736
Visiting infinitely recursive list: 133179499204736
Recursion detected! We're outta here, suckers!
Traceback (most recent call last):
  File "/home/leycec/tmp/mopy.py", line 48, in <module>
    dangerous_func_means_well(['Open-source', 'sells,' 'but', "who's", 'buying?'])
    ~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<@&#8203;beartype(__main__.dangerous_func_means_well) at 0x792044b9c360>", line 40,
 in dangerous_func_means_well
beartype.roar.BeartypeCallHintParamViolation: Function __main__.dangerous_func_means_well() parameter oh_gods=['Open-source', 'sells,but',
"who's", 'buying?'] violates type hint RecursiveListExplodesYourApp, as list index 2
item str "who's" not instance of list.

Pore one out for the unsuspecting @​beartype users that actually tried to run the above example. Their smoking CPUs are no longer with us. What remains of the ruin of their motherboards is now locked into a segfaulting bootloop featuring a cackling ASCII-art bear. It is sad.

@​beartype 0.21.0: "zomg so cuuuuuute oh my brain hurts nooooooooooooooo"

What's the Catch?

What? Catch? Surely you jest! There's no... oh, who am I kidding. There are huge catches associated with [PEP 695][]. For one, @​beartype intentionally does not support older PEP-noncompliant variants of recursive type hints that used stringified forward references. You might occasionally see crufty stuff like this floating around StackOverflow, older codebases, or the mypy issue tracker:

HorrifyingRecursiveTypeHint = Union[str, 'HorrifyingRecursiveTypeHint']

@​beartype doesn't support that. Using stringified forward references to induce recursion is non-standard. @​beartype probably could support that, but there's not much point in supporting non-standards when standardized alternatives exist. That's why...

@​beartype 0.21.0 only supports [PEP 695][]: the only standard for defining recursive type hints. Everything else was just something mypy made up. Recursive type aliases now work wonderfully under Python ≥ 3.12 – but that's the gotcha here.

@​beartype 0.21.0: rambo with a sword is something that happened only on an alternate timeline... but it still happened

Oh, Gods! Here It Comes!

That's right. You love to hate it. [PEP 695][] is unusable under Python ≤ 3.11. Attempting to define any type alias under Python ≤ 3.11 results in CPython raising an unreadable "SyntaxError: invalid syntax" exception.

In a year or two, this will be significantly less of a hard blocker for everyone. Increasingly, nobody cares about Python ≤ 3.11. Do you care about Python ≤ 3.11? Maybe – but you probably shouldn't, unless your huge userbase is obsessed by Python ≤ 3.11. In that case, you're kinda screwed. You have to choose between your love for recursion and your love for having users. Tough choice. I'd choose recursion, personally.

beartype 0.21.0: users who hate recursion are users who make your face contort into a tight rictus of agony

Python ≥ 3.12? Is That Really the Only Catch?

Absolutely! Totally! How could anything else possibly go wrong!

...oh, who am I kidding!?!?!? There is yet another huge catch associated with [PEP 695][]. @​beartype does not deeply t


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 these updates 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
@renovate renovate bot force-pushed the renovate/beartype-0.x branch from 58f409d to 062d899 Compare March 9, 2025 04:30
@renovate renovate bot changed the base branch from main to beta March 9, 2025 04:30
@renovate renovate bot force-pushed the renovate/beartype-0.x branch from 062d899 to 5d6be96 Compare March 9, 2025 05:33
@renovate renovate bot changed the base branch from beta to main March 9, 2025 05:33
@renovate renovate bot force-pushed the renovate/beartype-0.x branch from 5d6be96 to b67c499 Compare March 21, 2025 17:08
@renovate renovate bot changed the title fix(deps): update dependency beartype to >=0.20.0,<0.21.0 fix(deps): update dependency beartype to >=0.20.1,<0.21.0 Mar 21, 2025
@renovate renovate bot force-pushed the renovate/beartype-0.x branch from b67c499 to d1581b6 Compare March 22, 2025 19:37
@renovate renovate bot changed the title fix(deps): update dependency beartype to >=0.20.1,<0.21.0 fix(deps): update dependency beartype to >=0.20.2,<0.21.0 Mar 22, 2025
@renovate renovate bot force-pushed the renovate/beartype-0.x branch from d1581b6 to 104fbf8 Compare April 12, 2025 07:41
@renovate renovate bot force-pushed the renovate/beartype-0.x branch from 104fbf8 to 1aec2d0 Compare May 24, 2025 08:21
@renovate renovate bot changed the title fix(deps): update dependency beartype to >=0.20.2,<0.21.0 fix(deps): update dependency beartype to >=0.21.0,<0.22.0 May 24, 2025
@renovate renovate bot force-pushed the renovate/beartype-0.x branch from 1aec2d0 to 5689ca7 Compare August 15, 2025 07:59
@renovate renovate bot force-pushed the renovate/beartype-0.x branch from 5689ca7 to 4575db7 Compare August 23, 2025 07:19
@renovate renovate bot changed the title fix(deps): update dependency beartype to >=0.21.0,<0.22.0 chore(deps): update dependency beartype to >=0.21.0,<0.22.0 Aug 23, 2025
@renovate renovate bot force-pushed the renovate/beartype-0.x branch from 4575db7 to 17a89c8 Compare August 24, 2025 19:12
@renovate renovate bot changed the title chore(deps): update dependency beartype to >=0.21.0,<0.22.0 fix(deps): update dependency beartype to >=0.21.0,<0.22.0 Aug 24, 2025
@renovate renovate bot force-pushed the renovate/beartype-0.x branch from 17a89c8 to d0bb87b Compare September 17, 2025 03:26
@renovate renovate bot force-pushed the renovate/beartype-0.x branch from d0bb87b to 3b8c867 Compare October 5, 2025 06:58
@renovate renovate bot changed the title fix(deps): update dependency beartype to >=0.21.0,<0.22.0 fix(deps): update dependency beartype to >=0.22.2,<0.23.0 Oct 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