NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
PyPI · #364 most downloaded on PyPI
Unbearably fast near-real-time pure-Python runtime-static type-checker.
Last release 8 days ago
12 Sep 2026
Ships unpredictably
gaps range from 8 days to 8 months
Nearly every release is documented
notes for 50 of 54 stable releases
3 versions withdrawn
withdrawn after publishing
6 years old
66 releases · first in 2020
One column per quarter.
@beartype 0.19.0 gently glides into your CI workflow for a ~~crash~~ miracle landing. Engines go *brrrrrrrr.*
@beartype 0.19.0 gently glides into your CI workflow for a crash miracle landing. Engines go brrrrrrrr.
<sup>@beartype 0.19.0 narrowly avoids the grazing sheep in this terrifying metaphor.</sup>
@beartype 0.19.0 invites you to experience either the future of QA or a new catastrophe for QA – all from the comfort of your (t)rusty keyboard. It now thrums with untold power and the lurid afterglow of our out-of-control release cycle.
pip install --upgrade beartype # <-- engines hit full throttle, stomach hits full empty
@beartype 0.19.0 is proudly brought to you by...
This release comes courtesy these proud GitHub Sponsors, without whom @leycec's cats would currently be eating grasshoppers:
Thanks so much, masters of fintech and metrology.
<sup>The Masters of Fintech and Metrology. That's who.</sup>
<sup>Probably, a whole lot. Hopefully, a whole little. The truth lies in the middle.</sup>
@beartype 0.19.0 sidles up to your codebase in its blind spot with something suspicious in its paws. Questionable new features include:
beartype.door.infer_hint(): let BeartypeAI™ write your type hints for you, because you no longer have der Wille zur Macht to constantly deal with all this [redacted pejorative]:
# I've got a crazy object here, @beartype. What's the crazy type hint that
# matches my crazy object? This is gonna really suck. I can *FEEL* it coming
# through my monitor tonight.
>>> beartype.door.infer_hint(pygments.lexers.PythonLexer().tokens["root"])
list[typing.Union[tuple[str | collections.abc.Callable[
typing.Concatenate[object, object, ...], object], ...], tuple[str |
pygments.token._TokenType[str], ...], typing.Annotated[
collections.abc.Collection[str], beartype.vale.IsInstance[
pygments.lexer.include]]]] # <-- I have no idea. Neither does that cute intern.
beartype.claw.beartype_all() + BeartypeConf(claw_skip_package_names): a single one-liner type-checks your entire app stack at runtime or test-time while ignoring problematic third-party packages that inexplicably hate @beartype for "reasons":
beartype_all(conf=BeartypeConf(claw_skip_package_names=('bad_package', 'dumb.submodule')))
**kwargs: int | str: @beartype type-checks annotated variadic keyword arguments! yay! uhh... wait. wasn't @beartype always doing that? these emoji suggest otherwise: :smile: → :sob:
def i_am_simply_shocked(**kwargs: int | str): ... # <-- @beartype actually checks this now
Deeper O(1) type-checking. @beartype 0.19.0 now deeply type-checks type hints like:
frozenset[...].set[...].collections.ChainMap[...].collections.Counter[...].collections.deque[...].collections.abc.Collection[...].collections.abc.ItemsView[...].collections.abc.KeysView[...].collections.abc.MutableSet[...].collections.abc.Set[...].collections.abc.ValuesView[...].typing.AbstractSet[...].typing.ChainMap[...].typing.Collection[...].typing.Counter[...].typing.Deque[...].typing.FrozenSet[...].typing.ItemsView[...].typing.KeysView[...].typing.MutableSet[...].typing.Set[...].typing.ValuesView[...].Shallow O(1) type-checking support for exciting (yet wildly unpopular) PEP standards that nobody uses. @beartype 0.19.0 now quietly ignores these PEPs without throwing up everywhere:
def muh_decorator_closure(*args: P.args, **kwargs: P.kwargs):).Ts = typing.TypeVarTuple('Ts')).**kwargs typing (e.g., def muh_kwargs_func(**kwargs: typing.Unpack[MuhTypedDict]]):).Official multiprocessing support. @beartype 0.19.0 now officially supports fork-based distributed workloads based on the awful pickle module. :rofl:
Third-party decorator integration. @beartype 0.19.0 now officially supports popular Just-in-Time (JIT) decorators for machine learning (ML) like:
@equinox.filter_jit.@jax.jit.@numba.njit.Python 3.13 + --disable-gil + --enable-experimental-jit. Pinch me, I must be hyperventilating into a paper bag again.
Sane build toolchain: from setuptools + setup.py <sup> :vomiting_face: </sup> to Hatch + pyproject.toml. <sup> :clinking_glasses: </sup>
Sane publishing toolchain: from antiquated GitHub Actions tokens <sup> :vomiting_face: </sup> to PyPI-specific "Trusted Publishers". <sup> :clinking_glasses: </sup>
Critical bugs resolutions: blah, blah. Who cares. I'm tired. So are you.
<sup>@beartype 0.19.0 feature list mollifies even the unruly pirate crowd in the back</sup>
infer_hint(): Introducing BeartypeAI™, Your Chummy QA PalChummy Unpaid QA Pal BeartypeAI™ is on the job and grumbling already about union overtime. Because your team hates type hints (and you're reluctantly starting to admit they might be onto something), BeartypeAI™ does what nobody else wants to do. Authoring type hints is a thankless janitorial fetch quest that smells bad and consumes your last will to code.
Allow our new beartype.door.infer_hint() function to automate your ongoing stomache pain away. Type hints may be like that kidney stone the size of your mother-in-law's big head, but that's no reason to curl up on a gurney clutching your side in blinding agony. But first:
"What are type hints, really – aside from the second-worst ongoing maintenance nightmare in Python next to
pyproject.tomlsemantic versioning dependency bumps?"
Type hints are the most compact description of the internal structure of your objects. If you know the type hint of an object, you know the object better than the object knows itself. Type hints are the ultimate self-documentation. Unlike docstrings, type hints never lie or @beartype breaks your app. Type hints are both human-readable and machine-readable. They're literally the only thing that is.
Many type hints are trivial to write. All of us can sling around breezy list[int] | None type hints while yawning. It's not impressive. My cats can write that type hint with one sleepless eye open. Seriously. Why do cats sleep with one eye open, anyway? Doesn't that kinda defeat the purpose of... I dunno, sleeping? Must suck to be a paranoid cat. Uhhh. Back to the discussion.
Some type hints, however, are non-trivial. You can't write them. Nobody can. They contain more square brackets than an 80's ASCII roguelike with an understated name like Death Gehenna or Eternal Furnaces of NetSwargy. When your type hint looks like this, the end of code maintainability cannot be far:
<sup>that feeling when your type hints resemble incomprehensible toddlers</sup>
Moreover, you don't even know the internal structure of most objects. Somebody else wrote those objects. They forgot how those objects worked a hot minute after clocking out at 4:12AM seven days deep into a crunch-time death march last January. They documented how those objects worked, but their documentation doesn't make sense and lies about everything. Now nobody knows how those objects work.
But what if somebody did know how those objects work? What if somebody knew Python better than Python knew itself? Introducing... somebody.
infer_hint(): Deep Introspection for the Deep Code DiverLet @beartype ease your weary burden, traveller. It is dangerous to go alone:
# Crazy object you could understand. But... ain't nobody got that kinda time.
>>> from pygments.lexers import PythonLexer
>>> root_tokens = PythonLexer().tokens["root"]
# I've got a crazy object here, @beartype. What's the crazy type hint that
# matches my crazy object? This is gonna really suck. I can *FEEL* it coming
# through my monitor tonight.
>>> from beartype.door import infer_hint
>>> infer_hint(root_tokens)
list[ # <-- what could possibly go wrong?
typing.Union[ # <-- sucky stuff starts
tuple[str | collections.abc.Callable[typing.Concatenate[object, object, ...], object], ...], # <-- sucky stuff intensifies
tuple[str | pygments.token._TokenType[str], ...], # <-- so much sucky stuff
typing.Annotated[collections.abc.Collection[str], beartype.vale.IsInstance[pygments.lexer.include]] # <-- go to heck, typing!
] # <-- a pox on your square brackets
] # <-- i have no idea and neither do you
...uhh. If you say so, @beartype. I guess? </weeps_in_square_bracket_hell>
<sup>codebase narrowly dodges another inexpert potshot from pygments</sup>
beartype.door.infer_hint() (i.e., the algorithm hereafter known simply as BeartypeAI™) knows all about deep introspection of arbitrarily complex objects. Here's what BeartypeAI™ knows:
BeartypeAI™: "I know that your type hints kinda suck. Wait... where are your type hints? Oh, Gods. You don't type hint. I'm panicking. I'm panicking."
You: "Tell my team something my team don't know, BeartypeAI™. We hate type hints. So we don't type hint. We code instead. You should try it sometime. But... hey, aren't you a type-checking bear? Can you even code with those fat paws?"
BeartypeAI™: "Hold my QA beer."
BeartypeAI™ is here to tell you something you don't know and wouldn't care about even if you did.
BeartypeAI™ is here to write your type hints for you. Why? Because you hate type hints. Just:
beartype.door.infer_hint() arbitrarily complex objects.It's impossible to unpack how much madness is happening inside BeartypeAI™. Let's try anyway.
>>> from beartype.door import infer_hint # <-- all your dreams begin here
# Show me the type hint describing a useless object, @beartype!
>>> infer_hint(object())
<class 'object'> # <-- makes sense
# Show me the type hint describing a useless type, @beartype!
>>> infer_hint(object)
type[object] # <-- so. cool.
# Show me the type hint describing a list of strings, @beartype!
>>> infer_hint(['expose', 'extreme', 'explosions!',])
list[str] # <-- hole in one
# Show me the type hint describing a tuple of crazy stuff, @beartype!
>>> infer_hint((b'heh', [0xBEEEEEEEF, 'ohnoyoudont',]))
tuple[bytes, list[int | str]] # <-- no idea, but i trust it
# Show me the type hint describing an insane recursive list, @beartype!
>>> recursive_list = ['this is fine', b'but...',]
>>> recursive_list.append(recursive_list)
>>> infer_hint(recursive_list)
list[str | bytes | beartype.door._func.infer.inferhint.BeartypeInferHintContainerRecursion] # <-- just go with it
<sup>BeartypeAI™ pounds back another as your git log explodes in the distance</sup>
We've all been there. Some "genius"-tier wise guy devbro invented his own homebrew pure-Python collection called WeirdoCustomList without subclassing a standard collections.abc abstract base class. Sure, they could have just subclassed collections.abc.MutableSequence to write their special-needs alternative to the builtin list type. That would have been too easy. They're a masochist, so they wrote everything from scratch.
Homebrew collections are more common than you think. They're friggin' everywhere! They're multiplying like meat flies! The cat's choking on bloody homebrow collections! Look above, for example. See that nasty typing.Annotated[collections.abc.Collection[str], IsInstance[pygments.lexer.include]] type hint that BeartypeAI™ wrote for you? Yeah.
That's right. The public pygments.lexer.include type is actually a homebrew collection. They thought they were being smart. Sadly, they were being dumb. Because they wrote a homebrew collection from scratch, their collection type is unsubscriptable. You can't subscript it with child type hints like with list[str], so you can't use their collection type as a type hint factory to write type hints, so you can't actually validate their homebrew collection. In fact, you can't validate any homebrew collections.
...until now. BeartypeAI™ knows all about homebrew collections. BeartypeAI™ knows that you can actually validate homebrew collections – but only if you write a custom beartype validator leveraging the PEP 593-compliant typing.Annotated[...] type hint factory in concert with the @beartype-specific beartype.vale.IsInstance[...] validator. Please don't do this manually. You value your precious life force that is leaking all over your keyboard as we speak.
That's what the typing.Annotated[collections.abc.Collection[str], IsInstance[pygments.lexer.include]] type hint is all about. BeartypeAI™ correctly detected that this homebrew collection is actually just an instance of the pygments.lexer.include class that is externally usable as a collection of strings.
Let's get more explicit. Nobody understands pygments – not even pygments. Instead, consider...
<sup>BeartypeAI™ casually flings itself above another picturesque yet ultimately incomprehensible codebase</sup>
It's... it's hideous!
from beartype.door import infer_hint
from collections.abc import Iterable, Iterator
# Define a weirdo custom list.
class WeirdoCustomList(object):
'''
Weirdo custom list. The one. The only.
Weirdo custom list pretends to mean no harm. Weirdo custom list is
lonely at night and only wants to be snuggle-friends. *How could you
refuse weirdo custom list in its example of need?*
'''
def __init__(self, items: list) -> None: self._items = items
def __contains__(self, item: object) -> bool: return item in self._items
def __iter__(self) -> Iterator: return iter(self._items)
def __len__(self) -> int: return len(self._items)
def __getitem__(self, index: int) -> object: return self._items[index]
def __reversed__(self) -> Iterator: return reversed(self._items)
def count(self, item: object) -> int: return self._items.count(item)
def index(self, *args, **kwargs) -> int:
return self._items.index(*args, **kwargs)
def __delitem__(self, index: int) -> None: del self._items[index]
def __setitem__(self, index: int, item: object) -> None:
self._items[index] = item
def __iadd__(self, item: object) -> object: self._items += item
def append(self, item: object) -> None: self._items.append(item)
def clear(self) -> None: self._items.clear()
def extend(self, items: Iterable) -> None: self._items.extend(items)
def insert(self, index: int, item: object) -> None:
self._items.insert(index, item)
def pop(self, *args, **kwargs) -> object:
return self._items.pop(*args, **kwargs)
def remove(self, item: object) -> None: self._items.remove(item)
def reverse(self) -> None: self._items.reverse()
# Infer the type hint for a weirdo custom list of strings.
print(infer_hint(WeirdoCustomList([
'No way,', '@beartype.', 'No.', "Friggin'.", 'Way.'])))
...which prints:
typing.Annotated[collections.abc.MutableSequence[str], IsInstance[WeirdoCustomList]]
"What's so hot about that?", you may now be thinking. Allow me to now pontificate boringly.
WeirdoCustomList isn't subscriptable. It's not a type hint factory. Moreover, despite being a mutable sequence, WeirdoCustomList doesn't actually subclass the standard collections.abc.MutableSequence protocol. Yet, BeartypeAI™ correctly detected that this particular weirdo custom list is a mutable sequence of strings. How? It's best not to ask weirdo custom list these questions. :rofl:
<sup>wake up BeartypeAI™ when those type hints start making sense</sup>
Callable[...] Type Hints: Gods, They Suck.Annotating callables (especially callbacks) with PEP-compliant Callable[...] type hints is basically impossible. Personally, I've never gotten a single Callable[...] type hint to work right. They never match the callables they're supposed to when I write them myself. mypy and pyright always vomit all over themselves and then me. I mostly just give up now and use the unsubscripted collections.abc.Callable abstract base class instead of full-blown Callable[...] type hints...
...until now. BeartypeAI™ knows literally everything there is to know about annotating callables. What doesn't BeartypeAI™ know? Well, friends:
BeartypeAI™ knows that a lambda function accepting no parameters is annotated as...
>>> infer_hint(lambda: "No. Friggin'. Way.")
collections.abc.Callable[[], object] # <-- woah
BeartypeAI™ knows that a lambda function accepting multiple parameters is annotated as...
>>> infer_hint(lambda you, will, believe: "@beartype, I am your code father.")
collections.abc.Callable[[object, object, object], object] # <-- i don't know what's happening here, but i like it
BeartypeAI™ knows that a normal function accepting two mandatory annotated parameters and one optional annotated parameter is "best" annotated with a PEP 612-compliant typing.Concatenate[...] subscription as...
>>> def i_am_tired(this: float, so: int, boring: str = "y u so boring, @beartype!?"): ...
>>> infer_hint(i_am_tired)
collections.abc.Callable[typing.Concatenate[float, int, ...], object] # <-- don't ask, just accept.
BeartypeAI™ knows that a decorator wrapper function accepting a PEP 612-compliant parameter specification is annotated as...
>>> from typing import ParamSpec
>>> P = ParamSpec('P')
>>> def so_param_so_spec(*args: P.args, **kwargs: P.kwargs): ...
>>> infer_hint(so_param_so_spec)
collections.abc.Callable[~P, object] # <-- yer frickin' blowin' mah mind here, yo
BeartypeAI™ knows that a decorator wrapper function accepting two mandatory annotated parameters followed by a PEP 612-compliant parameter specification is annotated with a PEP 612-compliant typing.Concatenate[...] subscription as...
>>> from typing import ParamSpec
>>> P = ParamSpec('P')
>>> def more_param_more_spec(
... go_crazy: int,
... dont_mind_if_i_do: str,
... *args: P.args,
... **kwargs: P.kwargs
... ): ...
>>> infer_hint(more_param_more_spec)
collections.abc.Callable[typing.Concatenate[int, str, ~P], object] # <-- pretty sure the universe just exploded
What I'm trying to say here is that BeartypeAI™ knows all and sees all and doesn't like it what it sees, but is still doing it's best for everybody. It knows more than me. It probably knows more than even you, even though you know everything. That's how much BeartypeAI™ knows.
<sup>BeartypeAI™ takes the high road when it comes to Callable[...] type hints</sup>
Tensor type hints really suck. So your team wants to annotate NumPy, JAX, PyTorch, or TensorFlow arrays, huh? That's a perfectly reasonable request. Too bad, though. Because tensor type hints suck.
Tensor type hints suck so bad you have to use third-party packages like jaxtyping just to make them work, despite the fact that both NumPy and JAX ship type hint-centric subpackages like numpy.typing and jax.typing that are supposed to make tensor type hints "just work." Of course, tensor type hints don't "just work." They don't even work...
...until now. BeartypeAI™ knows literally everything there is to know about annotating tensor type hints. Actually, that's a lie. I really wanted BeartypeAI™ to know literally everything there is to know about annotating tensor type hints in time for @beartype 0.19.0rc1. Sadly, I played video games instead. I only got around to implementing BeartypeAI™ support for inferring NumPy tensor type hints.
Still, NumPy is better than nothing. One out of four ain't bad. Right? ...anybody? :face_exhaling:
# Define the greatest NumPy array that has ever existed.
>>> from numpy import asarray
>>> best_array_is_best = asarray((1, 0, 3, 5, 2, 6, 4, 9, 2, 3, 8, 4, 1, 3, 7, 7, 5, 0,))
# Create a type hint validating that array, @beartype! Look. Just do it.
>>> from beartype.door import infer_hint
>>> infer_hint(best_array_is_best)
typing.Annotated[numpy.NDArray[int], beartype.vale.IsAttr['ndim', beartype.vale.IsEqual[1]]] # <-- wtf, @beartype
And... that's the type hint. That type hint requires no third-party dependencies. It's all BeartypeAI™, all one-liner. Nobody's writing that sort of gruelling bracket hell on their own. Not even @leycec. Just let somebody else do your suffering for you. That somebody is BeartypeAI™. Who knew?
<sup>your codebase grips its hat as BeartypeAI™ boldly shows off for no reason</sup>
infer_hint() Time Complexity: All Roads Lead to O(1)infer_hint() is the first @beartype API to respect the long-standing BeartypeConf(strategy=BeartypeStrategy.O*)configuration option. Previously, *all* @beartype APIs defaulted toO(1)` constant-time behaviour by randomly sampling container items for improved scalability. In the future, all @beartype APIs will allow you to customize this behaviour by specifying alternate iteration strategies like:
O(n) linear-time behaviour, in which @beartype exhaustively examines all possible container items with recursion.O(log n) logarithmic-time behaviour, in which @beartype recursively examines only a logarithmic subset of all possible container items – a scalable compromise between non-deterministic O(1) immediacy and deterministic O(n) lethargy.Now, infer_hint() is the first @beartype API to fully support two of those three strategies. Witness as history unfolds with a discomfiting "plop!":
infer_hint(obj) is equivalent to infer_hint(obj, conf=BeartypeConf(strategy=BeartypeStrategy.On))). Under the O(n) strategy, infer_hint() exhaustively examines all possible container items with recursion. To infer authoritative type hints from interactive REPLs and Jupyter Notebooks, infer_hint() differs from the remainder of the @beartype codebase by defaulting to O(n)-style linear-time iteration. This is generally what most users "probably" want when inferring type hints. Since you are reading this, you are not one of those users.infer_hint(obj, conf=BeartypeConf(strategy=BeartypeStrategy.O1))). Under the O(1) strategy, infer_hint() pseudo-randomly examines only a single container item at each nesting level. This is generally what algorithms like structural similarity <sup>see below</sup> and multiple dispatch <sup>more seeing below</sup> want. To activate the Hyperlight Drive, these use cases want to explicitly pass a conf enabling the O1 strategy.BeartypeStrategy.O1: punch it, bald man!
<sup>@leycec punches it with fear in his heart</sup>
infer_hint() Use Cases: Where We Pontificate Both Laconically and LoquaciouslyWhat do those words even mean? Doesn't matter. Thankfully, what does matter is that type hint inference has real-world use cases that far exceed just "write my type hints for me, cause i h8 type hints m8. fr!" Just get a gander of these algorithmic goodies:
O(1) structural similarity comparison – faster even than ==-based equality comparison between arbitrary objects, which has worst-case O(n) linear-time complexity and thus scales poorly. Hyper-fast object comparison is what we sayin'.O(1) and worst-case O(k) multiple-dispatch for k the number of callables being dispatched to – probably the fastest multiple-dispatch algorithm in any language. Hyper-fast dispatch is what we still sayin'.Let's plumb these depths like Mario on a Piranha plant pipe bender.
<sup>@beartype smokes two bug-filled joints. then, @beartype smokes two more.</sup>
In the beginning, there was:
# The "is" operator. Test whether two objects are literally identical.
>>> "I like big bugs and I cannot lie." is "I like big bugs and I cannot lie."
True
# The "==" operator. Test whether two objects are semantically identical.
>>> ['Other', 'devbros', 'may', 'deny',] == ['Other', 'devbros', 'may', 'deny',]
True
But what if you want to test whether two objects are merely structurally similar (i.e., have a similar internal structure but are neither literally nor semantically identical)? Without BeartypeAI™, you can't do that. But you have BeartypeAI™. You no longer have to accept the mouldy table scraps that the standard Python library has left you.
Structural similarity compares the large-scale "shape" of two objects without regard for the small-scale minutiae (like the exact items) in those objects. Structural similarity thus combines:
is operator (like O(1) time complexity when calling infer_hint(obj, conf=BeartypeConf(strategy=BeartypeStrategy.On)))) with...== operator (like actually computing meaningful work) with...Tensors offer a useful way to understand structural similarity: "Do two tensors have the same dtype (i.e., type of all items in a tensor) and ndim (i.e., dimensionality)? If yay, those two tensors are structurally similar; if nay, those two tensors are structurally dissimilar."
There are two different kinds of structural similarity, broadly speaking:
>>> from beartype.door import infer_hint, is_bearable # <-- boring stuff
# @beartype builds excitement builds. Declare data structures for great glory of your code.
>>> awesome_data_structure = [{"hoh, boy": int, "lol, golgo 13": [lambda: None]}, 0xFAAAAAACE]
>>> baleful_data_structure = [0xDEAFDEFF, {"nopleaseno": [lambda: False], "NOOOO!": object}]
# Describe the internal structure of your final masterpiece.
>>> awesome_hint = infer_hint(awesome_data_structure)
list[int | dict[str, list[collections.abc.Callable[[], object]] | type[int]]] # <-- ok
>>> baleful_hint = infer_hint(baleful_data_structure)
list[int | dict[str, type[object] | list[collections.abc.Callable[[], object]]]] # <-- whateva you say, bear
# Do these two objects have the exact same internal structure?
>>> awesome_hint == baleful_hint
False # <----- no dice, huh? *sigh*
awesome_hint contains a dictionary mapping to integers, baleful_hint contains a dictionary mapping to merely objects. You are now thinking: "Integers are objects, you doltish man. Shouldn't we be able to ignore these awkward trivialities?" I object to being called a dolt while admitting you make a point. Make things vaguer by harnessing the perfidious power of beartype.door.infer_hint() + beartype.door.is_bearable(). Arise, substructural similarity! A new darkness!>>> from beartype.door import infer_hint, is_bearable # <-- boring stuff
# Excitement builds. Declare data structures for great glory of your code.
>>> awesome_data_structure = [{"uhh...": int, "wat!?!": [lambda: None]}, 0xFEEEEEEED]
>>> baleful_data_structure = [0xBABEEEE, {"ohgod": [lambda: True], "NOOOO!": object}]
# Describe the internal structure of your great glory.
>>> infer_hint(awesome_data_structure)
list[int | dict[str, list[collections.abc.Callable[[], object]] | type[int]]] # <-- ok
>>> infer_hint(baleful_data_structure)
list[int | dict[str, type[object] | list[collections.abc.Callable[[], object]]]] # <-- i don't know. sure, i guess?
# Do these two objects have a similar internal structure?
>>> is_bearable(baleful_data_structure, infer_hint(awesome_data_structure))
True # <----- WTF-F-F-F-F-
Structural similarity cheatsheet, because the one-liner is a harsh mistress:
infer_hint(obj_1) == infer_hint(obj_2).is_bearable(obj_1, infer_hint(obj_2)).Structural similarity: when you care about what your objects care about.
<sup>say goodbye to expensive comparisons that never really liked you anyway</sup>
"Dispatch" is a common decision problem in... well, basically any modern language that matters. So, not C. <sup>i have no regrets for igniting this flame war</sup>
Everyone's familiar with single-dispatch polymorphism, whereby an object-oriented language dynamically routes a call of an object's method to that object's "deepest" subclass overriding that method. In Python, we call this the method-resolution order (MRO) of an object. It's quite boring and pedantic stuff, really. I personally wouldn't click any of those links – especially not on the weekend.
But what if you want to perform single-dispatch outside of a class hierarchy that you directly control? Moreover, what if want to single-dispatch on arbitrary type hints deeply describing the internal structures of objects? Classes are superficial; they fail to fully convey the types of items contained in instances of those classes, which is why we do this type hint thing.
Moreover, what if we want to perform multiple-dispatch, whereby the callable that is dynamically routed to (i.e., called) depends not simply on the type of a single object but an arbitrary number of objects? This is the dynamical Hell we now find ourselves in.
Interestingly, it turns out that combining the beartype.door.infer_hint() + beartype.door.is_bearable() functions trivially yields highly efficient O(1) algorithms that transparently implement both single- and multiple-dispatch. First, the full-throttle single dispatch algorithm:
from beartype import BeartypeConf, BeartypeStrategy, beartype
from beartype.door import infer_hint, is_bearable
from collections.abc import Callable
_CONF_STRATEGY_O1 = BeartypeConf(strategy=BeartypeStrategy.O1)
'''
Beartype configuration enabling :math:`O(1)` constant-time random sampling.
'''
@beartype
def single_dispatch(obj: object, dispatcher: dict[object, Callable]) -> Callable:
'''
Callable suitable for dispatching the passed object from a type hint of the
passed dispatch dictionary.
Parameters
----------
obj : object
Object to be dispatched.
dispatcher : dict[object, Callable]
**Dispatch dictionary** (i.e., dictionary mapping from various type hints
to corresponding callables dispatching the passed object when that object
is validated by those type hints).
'''
# O(1) type hint inference, I choose you!
obj_hint = infer_hint(obj, conf=_CONF_STRATEGY_O1)
# Go for the O(1) short-circuit, @beartype. Do it.
dispatch_callable = dispatcher.get(obj_hint)
if dispatch_callable and is_bearable(obj, obj_hint):
return dispatch_callable
# Oh, noes! Disaster. Fallback to the O(k) iteration. Pretend this is okay.
for dispatch_hint, dispatch_callable in dispatcher.items():
if is_bearable(obj, obj_hint):
# Inject this inferred type hint and corresponding callable back into
# the dispatch dictionary, reducing the next call of this function
# passed a similar object to the O(1) short-circuit above. This
# guarantees amortized O(1) time complexity. </high_fives_all_around>
dispatcher[obj_hint] = dispatch_callable
return dispatch_callable
raise DispatchException(f'Passed object {repr(obj)} sucks. Blowing everything up!')
This exhibits time complexity:
O(1). <sup>oh by gods</sup>O(1). <sup>the gods glare with envy</sup>O(k) for k callables being dispatched to. <sup>the gods smugly look down and snicker</sup>Generally speaking, we expect k to be small in the average case – like, k < 10 small. So this is basically O(1) single-dispatch in even the non-amortized worst case.
Totally realistic and compelling usage resembles something like:
# User-defined callables to be dispatched to. Excitement builds.
def join_list_of_strs(lst: list[str]) -> str:
return ''.join(lst)
def join_list_of_bytes(lst: list[bytes]) -> bytes:
return b''.join(lst)
# User-defined object to be dispatched on. Excitement peaks.
list_of_things = [b'This. ', b'String. ', b'Bytes.']
# User-defined callable suitable for this object. Excitement subsides.
join_list_of_things = single_dispatch(list_of_things, dispatcher={
list[str]: join_list_of_strs,
list[bytes]: join_list_of_bytes,
})
# Pass this object to this callable. Excitement is in the gutter now.
assert join_list_of_things(list_of_things) == b'This. String. Bytes.'
That's probably the fastest possible single-dispatch algorithm in any language. But here's where the bullet train really goes off the rails...
<sup>@beartype flies foolishly close to the fathomless void so you don't have to</sup>
The single-dispatch algorithm trivially generalizes to multiple-dispatch as well. How? With cleverness, grit, and twin handlebar moustaches. :man: :man: <sup>← gritty moustauche twins</sup>
Let's reduce the multiple-dispatch case (of dispatching over multiple objects) to the single-dispatch case (of dispatching on a single object) by concatenating those multiple objects into a single object. Specifically, let's encapsulate those multiple objects into a tuple. Tuples are highly space- and time-efficient in Python. More importantly, tuples whose items are hashable are themselves hashable. By subscripting fixed-length tuple[...] types by the multiple type hints (almost all of which are hashable) to be dispatched across, we can actually leverage the almost exact same algorithm as above to perform O(1) multiple dispatch:
from beartype import BeartypeConf, BeartypeStrategy, beartype
from beartype.door import infer_hint, is_bearable
from collections.abc import Callable, Collection
_CONF_STRATEGY_O1 = BeartypeConf(strategy=BeartypeStrategy.O1)
'''
Beartype configuration enabling :math:`O(1)` constant-time random sampling.
'''
@beartype
def multiple_dispatch(
*args: object, dispatcher: dict[object, Callable]) -> Callable:
'''
Callable suitable for dispatching all objects in the passed collection from a
type hint of the passed dispatch dictionary.
Parameters
----------
*args : object
Tuple of all objects to be dispatched.
dispatcher : dict[object, Callable]
**Dispatch dictionary** (i.e., dictionary mapping from various type hints
to corresponding callables dispatching the passed objects when those objects
are validated by those type hints).
'''
# O(1) type hint inference, I choose you!
obj_hint = infer_hint(args, conf=_CONF_STRATEGY_O1)
# Go for the O(1) short-circuit, @beartype. Do it.
dispatch_callable = dispatcher.get(obj_hint)
if dispatch_callable and is_bearable(args, obj_hint):
return dispatch_callable
# Oh, noes! Disaster. Fallback to the O(k) iteration. Pretend this is okay.
for dispatch_hint, dispatch_callable in dispatcher.items():
if is_bearable(args, obj_hint):
# Inject this inferred type hint and corresponding callable back into
# the dispatch dictionary, reducing the next call of this function
# passed a similar object to the O(1) short-circuit above. This
# guarantees amortized O(1) time complexity. </high_fives_all_around>
dispatcher[obj_hint] = dispatch_callable
return dispatch_callable
raise DispatchException(f'Passed object {repr(obj)} sucks. Blowing everything up!')
That's... literally the exact same function. The signature just accepts variadic positional arguments *args rather than a single obj. Whatevah!
Crucially, this is still amortized worst-case O(1) and non-amortized worst-case O(k) multiple-dispatch for k the number of callables being dispatched. In other words, we have a cardinality-invariant dispatch algorithm. The time complexity of this algorithm is unconditionally O(k) regardless of the number of objects being dispatched over or the size of those objects. Since we generally expect k to be small, this is still basically O(1) multiple-dispatch. wuuuuuuuuuut
Totally realistic and compelling usage intensifies holistically:
# User-defined callables to be dispatched to. Excitement builds.
def join_list_of_strs_plus_str(lst: list[str], text: str) -> str:
return ''.join(lst) + text
def join_list_of_bytes_plus_bytes(lst: list[bytes], text: bytes) -> bytes:
return b''.join(lst) + text
# User-defined objects to be dispatched on. Excitement peaks.
list_of_things = [b'This. ', b'String. ', b'Still. ',]
plus_thing = b'Bytes.'
# Tuple of all user-defined objects to be dispatched over.
list_of_things_plus_thing = (list_of_things, plus_thing)
# User-defined callable suitable for these objects. Excitement subsides.
join_list_of_things_plus_thing = multiple_dispatch(*list_of_things_plus_thing, dispatcher={
tuple[list[str], str]: join_list_of_strs_plus_str,
tuple[list[bytes], bytes]: join_list_of_bytes_plus_bytes,
})
# Pass these objects to this callable. Excitement is in the gutter now.
assert join_list_of_things_plus_thing(*list_of_things_plus_thing) == b'This. String. Still. Bytes.'
That's definitely the fastest possible multiple-dispatch algorithm in any language. Can't do better than O(1). Suck it, Julia. Suck it.
I've tested that rickety jerry-rigged shadow madness. Against all odds... it somehow works. No idea how, honestly. Probably falls down in edge cases, honestly. But at least for one fleeting moment in the rain, we had a dream of something beautiful. :rofl:
<sup>O(1) multiple dispatch: the skull means it wants to help you</sup>
**kwargs: The Type-checking Chickens Come Home to RoostA few bicycle trips ago, my wife asked me a real eye-opener as the stinging sweat trickled down:
Why does "his chickens came home to roost" always mean that something bad just happened? Isn't it a good thing when the chickens come home to roost? Isn't that what chickens are supposed to do at night? Roost?
Me:
I see that you too have autism.
Seriously. Metaphors, folks. What good are they for if they make less sense than cats wearing pizza hats? Which leads us straight to...
Variadic keyword arguments. All this time, it was reasonable to believe that @beartype was type-checking annotated variadic keyword arguments like def func_in_a_funk(**kwargs: int). In actuality, @beartype was type-checking nothing there. Annotated variadic keyword arguments were silently ignored. Our flimsy reasons for doing nothing were fivefold:
The last reason is a good reason. The rest are bad reasons. Therefore, @beartype now type-checks annotated variadic keyword arguments as standardized by PEP 484 a decade ago.
The syntax is a bit odd, though. Although boring, this is worth belabouring. Python usually wants you to explicitly spell everything out. "Explicit is better than implicit" – except when it's not, apparently. All variadic keyword arguments are dictionaries mapping from strings (i.e., excess parameter names passed by keyword) to arbitrary objects (i.e., the values of those parameters). So far, so boring.
Since all variadic keyword arguments necessarily satisfy the type hint dict[str, object], however, Python interprets the type hint annotating a variadic keyword argument as the child value type hint of that dictionary. Thus, def oh_boy(**kwargs: float) is semantically equivalent to def oh_boy(kwargs: dict[str, float]) from the perspective of the body of oh_boy(). So far, still boring... albeit kinda weird and meandering now.
Above, I self-importantly wrote that "The types of the values of excess keyword arguments vary by keyword." The real-life embodiment of this is:
def fugly_muffin(**kwargs) -> int:
return kwargs['hear_nuffin'] + len(kwargs['see_nuffin'])
assert ugly_muffin(hear_nuffin=0xDEAF, see_nuffin='NOPE.') == 57012
Above, the first excess keyword parameter hear_nuffin has type int while the second excess keyword parameter see_nuffin has type str. How do you type this madness as a single type hint when the types vary by keyword? Simple:
The Beartype-Friendly Way, which is also the awful way. This approach has the benefit of being supported by @beartype, but the drawback of sucking. You ambiguously type **kwargs as the union of the types of the values of all excess keyword arguments: e.g.,
# This is trash. What you goin' do?
def fugly_muffin(**kwargs: int | str) -> int:
return kwargs['hear_nuffin'] + len(kwargs['see_nuffin'])
The PEP 692 Way, which is honestly also kinda awful. This has the benefit of being unambiguous (unlike the "Beartype-Friendly Way" above), but the multiple drawbacks of not currently being supported by @beartype, requiring Python ≥ 3.12, and expanding into 500 lines of sad-faced boilerplate that will break your will to sling code on Friday nights. Under PEP 692, you type **kwargs as the typing.Unpack of a typing.TypedDict subclass that you define just to unambiguously constrain the types of the values of all excess keyword arguments: e.g.,
# This is still trash -- merely a different and larger kind of trash.
from typing import TypedDict, Unpack
class _GodsThisIsTrashy(TypedDict):
'''
You didn' see nuffin' except 500 lines of boilerplate that burn the eyes.
'''
hear_nuffin: int
see_nuffin: str
def fugly_muffin(**kwargs: _GodsThisIsTrashy) -> int:
'''
Still trashy after all these years.
'''
return kwargs['hear_nuffin'] + len(kwargs['see_nuffin'])
Even if @beartype supports PEP 692 by the time you read this, <sup>Spoiler from the sad future: ...it still doesn't!?</sup> I'd still personally opt for the @beartype-friendly def fugly_muffin(**kwargs: int | str): approach. Sure, it's ambiguous. But it's also a trivial 9 characters rather than 500 lines of sad-faced boilerplate. Consider the number of callables that accept **kwargs in your codebase. Are you really gonna define one unique TypedDict subclass just to unambiguously annotate each **kwargs parameter? Really? Some of us might think we are. But then we try and fall down clutching our rib cages. The finger-breaking reality of that much boilerplate has broken greater devs than us before.
I scoff into my "Revenge of the Nerds"-era pocket protector. Annotating variadic keyword arguments may still suck after all these years – but at least @beartype now supports the slightly less sucktastic way.
<sup>five out of ten men who are pigs support this feature. do you?</sup>
Let's create a fake PEP and pretend it exists. In other words, this subsection is of no value to anyone whatsoever. Still, unhinged dreams exist for a reason. If I was a CPython typing dev, this is the Mirror World PEP 692 that I personally would have written to trivialize **kwargs type hints:
from typing import Unpack
def fugly_muffin(**kwargs: Unpack['hear_nuffin': int, 'see_nuffin': str]) -> int:
return kwargs['hear_nuffin'] + len(kwargs['see_nuffin'])
Trivial. Right? Just subscript typing.Unpack[...] with dictionary-like key-value pairs of the names and types of all excess keyword arguments accepted by that callable. This is already valid Python syntax as shown above. No changes to the CPython PEG (Parser Expression Grammar) or parser are required. This syntax promotes trivial, readable, maintainable, debuggable one-line type hints for **kwargs. No extraneous 500-line TypeDef subclasses or whatevah boilerplate are required.
Moreover, this same syntax easily generalizes to Callable[...] type hints. Currently, Callable[...] type hints fail to support keyword arguments; they only support positional-only parameters. Since nobody uses positional-only arguments, Callable[...] type hints are basically useless as defined. Instead, Callable[...] type hints could be readily extended using the exact same syntactic mechanism to support both keyword and keyword-only arguments: e.g.,
from collections.abc import Callable
# This should, like, totally be the way you annotate callbacks in Python and stuff.
def call_back(callback: Callable[['trust_me_bro': int, 'grifter': str], int]) -> int:
return callback(trust_me_bro=42, grifter='y u so grifty, Grifty McGrift?')
Dictionary-like syntax should totally be the standard way to type keyword parameters. We know I mean standardization business, because I just used the word "totally."
<sup>man-pig ponders the existential nature of bad standards, as nobody cares</sup>
beartype_all(): It Actually Works Now, KindaAh, yes. The venerable beartype.claw.beartype_all() import hook. Anybody remember that thing? Me neither. Nobody uses that thing, because that thing blows up whenever you look at it. Let's back up.
beartype_all() unconditionally type-checks literally everything. Whereas beartype_this_package() only type-checks your package, beartype_all() type-checks both your package and everybody else's packages too. <sup>Fake footnote: Technically, this includes even the standard CPython library. Pragmatically, the standard CPython library contains no type hints whatsoever. Why? Because CPython devs hate runtime typing. This fake footnote means nothing. I wasted my time writing this. You wasted your time reading this. We cry tears in the rain together.</sup>
It's the "everybody else's packages too" part that is the problem there. Although you expect your package to be type-checked with @beartype, nobody else does. Nobody expects their package to be type-checked with @beartype without their permission or knowledge – at least, not until you open 317 pending issues on their issue tracker enumerating every shocking yet mundane "error" in their package when type-checked with @beartype. This is why beartype_all() fails you when you need it most. You can't control other people's intransigence towards the Bear... until now.
Introducing the new BeartypeConf(claw_skip_package_names: Collection[str] = ()) configuration option! claw_skip_package_names is a package name blacklist (e.g., ban, deny, ignore, or omit list of the names of all packages and modules to be excluded from consideration), enabling you to selectively ignore one or more problematic third-party packages when type-checking the entire Universe via beartype_all(). The Universe just got a little smaller and a lot smarter, folks.
Because claw_skip_package_names is sane:
list, tuple, set, whatevahs).'bad_apple').'lovely_cat.ugly_dog').Because reality is even more disappointing than public education prepared us for, the claw_skip_package_names "option" is basically mandatory. It's not optional despite being called an option. Whenever you call beartype_all(), you also need to pass claw_skip_package_names: e.g.,
from beartype import BeartypeConf
from beartype.claw import beartype_all
# Beartype everything except sucky packages that hate everything good in the world.
beartype_all(conf=BeartypeConf(claw_skip_package_names=(
'some_sucky_package',
'another_package_hates_you',
'this_package_is_great.this_submodule_is_trash',
)))
claw_skip_package_names: because the Universe is kinda like in the Aliens franchise.
<sup>when you fly with @beartype, you fly with a man who is a pig</sup>
beartype_all() + pytest-beartype: They Were Meant for Each Otherpytest-beartype plugin users are now thoughtfully chewing their upper lips and thinking:
What about us!? We deserve better, too. Neglect us and we'll burn this issue tracker down.
You do you. That's why pytest-beartype maintainer (and all-around Python-Zig tooling God) @tusharsadhwani has already implemented support for command-line equivalents of both the beartype_all() import hook and the claw_skip_package_names option. In short, just:
pytest --beartype-packages='*' --beartype-skip-packages='awful_package, horrible.submodule'
Let's unpack this. CLI stuff is always so crufty, isn't it? Passing:
--beartype-packages='*' instructs pytest-beartype to internally call the universal beartype_all() import hook rather than the local beartype_packages() import hook. Good.--beartype-skip-packages blacklists those packages and modules from consideration. Good.--beartype-skip-packages: because not all heroes write one-liners.
<sup>sick burn, pig-man. but what does this have to do with @beartype? the answer may shock somebody.</sup>
O(1) Type-checking@beartype 0.19.0 deeply type-checks a ton of fun containers I've loosely dubbed reiterables.
A reiterable is a collection satisfying the collections.abc.Collection protocol with guaranteed O(1) read-only access to only the first collection item. Reiterables include sets, frozen sets, dictionary views, deques (i.e., double-ended queues), and all other containers matched by one or more of the following PEP 484- or 585-compliant type hints:
frozenset[...].set[...].collections.ChainMap[...].collections.Counter[...].collections.deque[...].collections.abc.Collection[...].collections.abc.ItemsView[...].collections.abc.KeysView[...].collections.abc.MutableSet[...].collections.abc.Set[...].collections.abc.ValuesView[...].typing.AbstractSet[...].typing.ChainMap[...].typing.Collection[...].typing.Counter[...].typing.Deque[...].typing.FrozenSet[...].typing.ItemsView[...].typing.KeysView[...].typing.MutableSet[...].typing.Set[...].typing.ValuesView[...].@beartype now deeply type-checks almost all of the core PEP 484 and 585 standards, resolving feature request #167 kindly submitted by the perennial brilliant @langfield (...how I miss that awesome guy!) several lifetimes ago back when I was probably a wandering vagabond Buddhist monk with a bad attitude, a begging bowl the size of my emaciated torso, and an honestly pretty cool straw hat that glinted dangerously in the firelight.
There's still a bit of low-hanging fruit dangling its juicy skin here and there – but not much. The biggest offenders that have yet to be deeply type-checked are:
Iterable[...] type hints. Not hard, so I claim. Just needs a bit of spit and polish, so I claim. I'm claiming lots of things without hard evidence here.
Callable type hints (e.g., collections.abc.Callable[...], typing.Callable[...]). Thankfully, BeartypeAI™ now provides a trivial O(1) one-liner for deeply type-checking any callable type hint hint against any arbitrary callable func:
def is_func_bearable(func: Callable, hint: object) -> bool:
return is_subhint(infer_hint(func), hint) # <-- lolbro
Type variables (e.g., typing.TypeVar('T'). Still no idea how to dynamically generate efficient code type-checking type variables, honestly. It's feasible, but let's avoid thinking about this until there's absolutely nothing left to do. :sweat_smile:
...how is to possible that so much and yet so little has changed? Please manage our time better or we're never gonna cross that finish line, GitHub. @leycec assumes no responsibility for just playing video games for a year.
@beartype: It's actually starting to do stuff, now.
<sup>if i'm reading these schematics right, @beartype actually does stuff now</sup>
@beartype's love for PEP 612 – Parameter Specification Variables, PEPs646 – Variadic Generics, and PEP 692 – Using TypedDict for more precise **kwargs typing may be a tepid pool of mucky brackish water you can barely dip your toes into – but at least @beartype 0.19.0 tried, daggumit.
@beartype 0.19.0 now supports you in your aspirations to obfuscate decorator closures beyond the dark horizon of MIT Python obfuscation competitions by silently ignoring those aspirations:
# Guido himself defined a decorator so complex it "logs to a database"
# while exploding @beartype with soul-sucking parameter specifications.
from typing import Awaitable, Callable, ParamSpec, TypeVar
P = ParamSpec("P")
R = TypeVar("R")
def add_logging(f: Callable[P, R]) -> Callable[P, Awaitable[R]]:
async def inner(*args: P.args, **kwargs: P.kwargs) -> R:
await log_to_database()
return f(*args, **kwargs)
return inner
@beartype doesn't pretend to understand what typing.ParamSpec('P').kwargs means, but @beartype doesn't have to. @beartype is here to crush bugs and play video games... and @beartype is all outta video games.
<sup>that one unforgettable moment when @beartype 0.19.0 reveals its true nature</sup>
TypedDict Is Useful for Something@beartype 0.19.0 now supports you in your aspirations to precisely type-check **kwargs by silently ignoring those aspirations:
from beartype import beartype
from typing import TypedDict, Unpack
class Kwargs(TypedDict):
this_kwarg_must_be_a_string: str
this_kwarg_must_be_a_complex_number_just_kidding_its_actually_an_integer: int
@beartype
def function_accepts_two_kwargs(**kwargs: Unpack[Kwargs]) -> None: ...
That's better than @beartype used to do (which was blow chunks everywhere). We don't deeply type-check this yet, but we will. Would @leycec lie!? :sweat:
<sup>newer and sleaker @beartype does a surprise fly-by over your codebase. hats are almost lost.</sup>
It's all tuples all the way down with PEP 646.
@beartype 0.19.0 now supports you in your aspirations to precisely type-check... actually, I really have no idea. But that's okay, because neither does @beartype 0.19.0. What is PEP 646 besides really confusing? Couldn't tell ya. All I know is that @beartype now silently ignores PEP 646-compliant type variable tuples (i.e., typing.TypeVarTuple objects):
from beartype import beartype
from typing import TypeVar, TypeVarTuple
DType = TypeVar('DType')
Shape = TypeVarTuple('Shape')
@beartype
class Array(Generic[DType, *Shape]):
def __abs__(self) -> Array[DType, *Shape]: ...
def __add__(self, other: Array[DType, *Shape]) -> Array[DType, *Shape]: ...
I kinda get it, but I kinda don't. Since @beartype 0.19.0 doesn't get it any more than I do, @beartype doesn't deeply type-check TypeVarTuple objects yet. Will it ever? No idea. Let's pretend:
"Yes! Absolutely! All your dreams will be realized by... @beartype 42.42.42!?"
@beartype doesn't even deeply type-check TypeVar objects yet – which is the slightly lower-hanging fruit here. Oh, when will free time materialize for @leycec? What has @beartype done to deserve this punishing development schedule? I fear for your immortal git log, @beartype. :fearful:
<sup>@beartype 42.42.42 chortles as it contemplates the darkness of the past</sup>
@beartype 0.19.0 officially supports the standard multiprocessing API for fork-based distributed workloads. All beartype exceptions (i.e., exception subclasses published by the beartype.roar subpackage) now support pickling and unpickling via the standard pickle module, which then suffices to support the standard multiprocessing package, which shockingly leverages pickle rather than dill in 2024.
@beartype 0.19.0: "Why is pickle still even a thing!?"
<sup>You're stupid, multiprocessing. I hate that in an API.</sup>
@beartype 0.19.0 goes hard on integration with external decorators published by third-party packages that previously hated @beartype. This includes popular Just-in-Time (JIT) decorators for machine learning (ML) like:
@equinox.filter_jit.@jax.jit.@numba.njit.@beartype should now support almost everybody else's decorators. In fact, @beartype now generically supports all pseudo-callable wrapper objects (i.e., objects defining both the __call__() and __wrapped__ dunder attributes).
The @beartype decorator should also now be context-free. You may now chain (i.e., list) @beartype above or below most third-party decorators. Since beartype.claw import hooks (like beartype_this_package() and beartype_package()) inject @beartype above all other decorators, beartype.claw import hooks now transparently support all other decorators... probably. :grimacing:
If you previously blacklisted @beartype from type-checking callables decorated by any of the above with @typing.no_type_check, let us give thanks as you remove @typing.no_type_check everywhere.
Examples or it only happened in the DMT hyperplane:
from beartype import beartype
from jax import (
jit,
numpy as jax_numpy,
)
from jaxtyping import (
Array,
Float,
)
@beartype # <-- *GOOD*. @beartype goes last! patiently suffer in silence, @beartype.
@jit # <-- *GOOD*. @jax.jit goes first! yoink.
def what_would_chat_gpt_do(
probably_hallucinate_everything: Float[Array, '']) -> Float[Array, '']:
# One-liner: "Do what I say, not what I code."
return probably_hallucinate_everything + 1
assert what_would_chat_gpt_do(jax_numpy.array(1.0)) == jax_numpy.array(2.0)
what_would_chat_gpt_do('If this is a JAX array, we all have serious problems.')
...which raises the expected type-checking violation:
Traceback (most recent call last):
File "/home/leycec/tmp/mopy.py", line 22, in <module>
what_would_chat_gpt_do('If this is a JAX array, we all have serious problems.')
File "<@beartype(PjitFunction.__call__) at 0x7fd9afc96140>", line 29, in __call__
beartype.roar.BeartypeCallHintParamViolation: Object
PjitFunction.__call__() parameter probably_hallucinate_everything='If
this is a JAX array, we all have serious problems.' violates type hint
<class 'jaxtyping.Float[Array, '']'>, as str 'If this is a JAX array, we
all have serious problems.' not instance of <protocol
"jaxtyping.Float[Array, '']">.
The perspicacious user may now be thinking:
"WAIT. What is a
PjitFunction.__call__()? That's ambiguous and means less than my cat licking itself. Your type-checking violation message sucks, huh?"
You're not wrong. But we're tired. At least @beartype works now for various definitions of "works." If you just hit this ambiguous type-checking violation message in your workflow and want @beartype to justifiably do something about it, bang on our issue tracker until the cats start squalling and biting @leycec in the face. Works every time.
@beartype 0.19.0: we broke our sanity for your security.
<sup>@beartype 0.19.0: it's been a long journey, fam.</sup>
@beartype 0.19.0 officially supports Python 3.13, the first official CPython release you no longer need to feel ashamed of running in public.
Python 3.13 supports an official LLVM-based Just-in-Time (JIT) compiler via the PEP 744-compliant --enable-experimental-jit compile-time option. OMMMMMMMMMMMMMMMMG..... It's happening. It's really happening. My breathing is now laboured and making awkwardly squishy noises that upset the cat.
Python 3.13 also supports no-GIL GIL-free multi-threading via the PEP 703-compliant --disable-gil compile-time option. Yes! YES! YEEEEEEESSSSS!!!! Wait. Where am I? What are these fingers on this keyboard? This must be what Xanadu is typed of.
If I worked on a proprietary Python package, I'd have money. I'd also:
python >=3.13 for production workloads as soon as CPython 3.13 lands in October.--enable-experimental-jit for development workloads.It's time to go fast. Finally, it's time to feel shameless.
<sup>OMG IZ @beartype + CPython 3.13 + --enable-experimental-jit WTF FAFO!!!</sup>
@beartype 0.18.0 broke the entire world. @leycec can now admit that to himself while clutching his Maine Coon teddy cat. If your codebase survived @beartype 0.18.0, you deserve an "I Survived @beartype 0.18.0 and All I Got Was This Lousy Badge" badge. The ill-fated @beartype 0.18.0 release cycle that nearly broke my fingers taught me many things: suffering, pain, agony, blah, blah... You know. Just the standard stuff, really.
@beartype 0.18.0 taught me that @beartype has become a lot bigger than me. Other people and people-like AI that are doing meaningful things with their lives and synthetic lives (respectively) now depend on new @beartype releases not throwing up all over everybody.
@beartype ≥ 0.19.0 intends to avoid that throw-up. Several days before releasing any new major version like 0.19.0, 0.20.0, or 0.21.0: <sup>...we see the number sequence I trust</sup>
Basically, I'm just doing standard beta releases now. That's all I had to say. Instead, I laboriously enumerated a workflow that doesn't really make sense when you squint at it. Oh, well. This too was wasted time.
The sins of the fathers must never be repeated. Never forget @beartype 0.18.0! Never forgive @leycec! Wait. Shouldn't @leycec be forgiven already at some point!? <sup><-- dat poor guy</sup>
<sup>@beartype 0.18.0: shocking behind-the-scenes tell-all reveals sordid truth of what went wrong that fateful day</sup>
In discussion thread #433, @jedie wisely asks the question we're all wondering:
There are many, many commits since last release: https://github.com/beartype/beartype/compare/v0.18.5...main
What's the release cycle? Seems it's not:
Release early, release often
isn't it?
Indeed, @jedie. It isn't. You're right about everything. I now quote myself like a narcissist. <sup>gods what am i become</sup>
For ordinary packages, "Release early, release often" is the best possible advice. For @beartype, this is the worst possible advice. Why? Because @beartype is mission-critical. When @beartype breaks, increasingly the entire Python ecosystem breaks. This includes PyTorch – which then transitively includes ChatGPT, OpenAI, Microsoft, and by extension the entirety of American late-stage capitalism. Do we grok the stakes here? The stakes somehow become a whole lot more bigly than "one bald autist has fun smashing code together in a remote Canadian cottage."
I should probably be paid to do this hyper-cuboidal tesseract we call @beartype. Imagine if all neurosurgeons were unpaid volunteers. This is hyperbole, but it's also not. @beartype is the neurosurgeon that fixes bugs during LLM training. Much like Soviets under the USSR, I pretend that I'm being paid by behaving responsibly towards the rest of humanity. "Release early, release often" is what I used to believe. Then I broke PyTorch with the ill-fated @beartype 0.18.0 release. Now, I choose wisely.
The new motto is:
Release late. Release rarely. Release safely.
On the bright side, "Release safely." is good! We can all agree. On the dark side, "Release late." and "Release rarely." are both bad. We still agree. But one out of two ain't bad. Right?
New @beartype release will probably land as follows:
0.19.0) once every six months or so.0.19.0rc0) once every month or so.This broadly parallels CPython's shift to a yearly release schedule with intermittent mid-yearly alpha and beta pre-releases. Since @beartype isn't as bigly as CPython, we can and should go faster and harder than CPython on releases – but we can't go that much faster or harder. Realistically speaking, @beartype releases will remain slower than your average open-source Python package.
Sucks, huh? I know and commiserate by blowing smoke out of gigantic nostrils on a picturesque beach.
<sup>that feeling when you're only in month 1 of an interminable 6-month release cycle</sup>
pyproject.toml: The Build System We Deserved 10 Years Ago, Today@beartype 0.19.0 now sports a sane build system. It's sporty! Somehow, we found the strength to refactor the archaic @beartype 0.18.0 toolchain from setuptools + setup.py <sup> :vomiting_face: </sup> to Hatch + pyproject.toml <sup> :clinking_glasses: </sup>. This includes support for modern packaging standards like PEP 517 and 621.
It went great, actually. Thanks for asking. I highly recommend Hatch for all projects – new and curmudgeonly alike. It's like Rust's Cargo, only Python. It actually works, unlike everything else.
Hatch: because you're too bald to fight Python anymore.
Caveat emptor:
- For most users, this doesn't matter. Celebrate.
- For package maintainers like @harens (...I'm so sorry), this means that all third-party @beartype packages in the wild now need to be manually bumped to depend on Hatch (rather than
setuptools) at build time. If your packaging ecosystem also packages Hatch, this is trivial. Else, I sympathize with your growing toothache but can do nothing for you. Emoji man sighs. :face_exhaling:
<sup>@beartype 0.19.0 now gives thanks for this build system it is about to blow up</sup>
@beartype 0.19.0 now sports a sane publishing system. It's less insane! Somehow, we found the stamina to refactor the archaic @beartype 0.18.0 release workflow from antiquated (and unsurprisingly insecure) GitHub Actions tokens :vomiting_face: to PyPI-specific "Trusted Publishers" (i.e., PyPI's modern implementation of OpenID Connect (OIDC)). :shrug:
In theory, doing so should resolve the current plethora of "Unverified details" that currently pollutes @beartype's PyPI project page. We're not unverified, PyPI! You're unverified.
In practice, doing so will almost certainly change nothing and thus have no benefit whatsoever. Indeed, doing so will probably prevent our entire release workflow from behaving as expected – further squandering scarce open-source volunteerism for no particularly good reason whatsoever.
Bureaucracy: "What is it good for when @leycec could just be playing video games about robot assassins who insist they meant well instead?"
<sup>@beartype 0.19.0: on its way to a PyPI project page near you</sup>
Announcing all the fave @beartype users from the ashes of our issue tracker:
@posita, @wesselb, @iamrecursion, @patrick-kidger, @langfield, @JelleZijlstra, @RobPasMue, @GithubCamouflaged, @kloczek, @uriyasama, @danielgafni, @JWCS, @rbroderi, @AlanCoding, @tvdboom, @crypdick, @jvesely, @komodovaran, @kaparoo, @MaximilienLC, @fleimgruber, @EtaoinWu, @alexoshin, @gabrieldemarmiesse, @James4Ever0, @NLPShenanigans, @rtbs-dev, @yurivict, @st--, @murphyk, @dosisod, @Rogdham, @alisaifee, @denisrosset, @damarro3, @ruancomelli, @jondequinor, @harshita-gupta, @jakebailey, @denballakh, @jaanli, @creatorrr, @msvensson222, @avolchek, @femtomc, @AdrienPensart, @jakelongo, @Artur-Galstyan, @ArneBachmann, @danielward27, @WeepingClown13, @rbnhd, @radomirgr, @rwiegan, @brettc, @spagdoon0411, @helderco, @paulwouters, @jamesbraza, @dcharatan, @kasium, @AdrienPensart, @sunildkumar, @peske, @mentalisttraceur, @awf, @PhilipVinc, @dcharatan, @empyrealapp, @rlkelly, @KyleKing, @skeggse, @RomainBrault, @pablovela5620, @thiswillbeyourgithub, @WeepingClown13, @JWCS, @Logan-Pageler, @knyazer, @Moosems, @frrad, @minmax, @jaanli, @jonnyhyman, @f-fuchs, @jennydaman, @denballakh, @bionicles, @taranlu-houzz, @adamtheturtle
<sup>center right: your codebase. center left: @beartype. everybody else: @beartype's competition, which doesn't of course exist.</sup>
Finally. The Big Bear has landed... *yet again!?* This better be good, @beartype 0.19.0 Release Cycle from Hell.
Finally. The Big Bear has landed... yet again!? This better be good, @beartype 0.19.0 Release Cycle from Hell.
@beartype 0.19.0 release candidate 2 – the third (and seriously better-be final) beta pre-prelease of the zombified @beartype 0.19.0 release cycle that I should have released three four months ago – lands in your lap with a sickening ploppy! sound. Tragically, typing pro @minmax discovered a last-minute critical issue blocking the release of @beartype 0.19.0 proper. With that issue now unblocked, the smelly Internet Tubes are now ready for @beartype 0.19.0 proper...
...assuming nothing else is broke. Is anything else broke? No idea. Probably. It's @beartype 0.19.0, after all. This thing will never ship. Indeed, the struggle for your codebase is like a rugby match between death robots:
pip install --upgrade --pre beartype # <-- sound of bugs panicking distantly heard
@beartype 0.19.0rc2 is brought to you by...
This release comes courtesy these proud GitHub Sponsors, without whom @leycec's cats would currently be eating grasshoppers:
Thanks so much, masters of fintech and metrology.
<sup>The Masters of Fintech and Metrology. That's who.</sup>
0.19.0rc2 Actually Do?Too tired. Next question, please. :sleeping:
In discussion thread #433, @jedie wisely asks the question we're all wondering:
There are many, many commits since last release: https://github.com/beartype/beartype/compare/v0.18.5...main
What's the release cycle? Seems it's not:
Release early, release often
isn't it?
Indeed, @jedie. It isn't. You're right about everything. I now quote myself like a narcissist. <sup>gods what am i become</sup>
For ordinary packages, "Release early, release often" is the best possible advice. For @beartype, this is the worst possible advice. Why? Because @beartype is mission-critical. When @beartype breaks, increasingly the entire Python ecosystem breaks. This includes PyTorch – which then transitively includes ChatGPT, OpenAI, Microsoft, and by extension the entirety of American late-stage capitalism. Do we grok the stakes here? The stakes somehow become a whole lot larger than "one bald autist has fun smashing code together in a remote Canadian cottage."
I should probably be paid to do this immense and insane thing we call @beartype. Imagine if all neurosurgeons were unpaid volunteers. This is hyperbole, but it's also not. @beartype is the neurosurgeon that fixes bugs during LLM training. Much like Soviets under the USSR, I just pretend that I'm being paid by behaving responsibly towards the rest of humanity. "Release early, release often" is what I used to believe. Then I broke PyTorch with the ill-fated @beartype 0.18.0 release. The heavens themselves unleashed Hellish Displeasure. Now, I choose wisely.
The new motto is:
Release late. Release infrequently. Release safely.
On the bright side, "Release safely." is good! We can all agree. On the dark side, "Release late." and "Release infrequently." are both bad. We still agree. But one out of two ain't bad. Right?
<sup>that feeling when you realize it isn't even two out of three</sup>
Finally. The Big Bear has landed.
Finally. The Big Bear has landed.
@beartype 0.19.0 release candidate 1 – the second (and better-be final) beta pre-prelease of the zombified @beartype 0.19.0 release cycle that I should have released three months ago – lands in your lap with a suspicious quacking noise. You're startled. You shriek in dismay! And that's when you hear it. A knock on the door. Muffled voices from the hallway. Disconcertingly familiar, you feel like you almost understand what's going on:
Code Lieutenant: I think we can handle one little beta release. I sent two unit tests. They're bringing it down now. Agent Leycec: No, Code Lieutenant. Your bugs are already dead.
@beartype 0.19.0rc1 politely hiccups. Yet the struggle for your codebase has only just begun:
pip install --upgrade --pre beartype # <-- sound of bugs panicking distantly heard
@beartype 0.19.0rc1 is brought to you by Ultros, a purple octopus in dire need of dental work.
<sup>your bugs are up the creek without a paddle! and @beartype isn't going to let them through! does that make @beartype a good Bear?</sup>
Okay. Okay. Ultros didn't do nuffin' except lie about on a raft all day and try to eat heroes. Let's try again.
@beartype 0.19.0rc1 is actually brought to you by...
This release comes courtesy these proud GitHub Sponsors, without whom @leycec's cats would currently be eating grasshoppers:
Thanks so much, masters of fintech and metrology.
<sup>The Masters of Fintech and Metrology. That's who.</sup>
infer_hint(): Because you hate type hints, make @beartype write them all for youType hints are the most compact description of the internal structure of your objects. If you know the type hint for an object, you know the object better than the object knows itself. Type hints are thus the optimal documentation. Unlike docstrings, type hints never lie or @beartype breaks your app. Type hints are both human-readable and machine-readable. They're literally the only thing that is.
Many type hints are trivial to write. All of us can sling around breazy list[int] | None type hints while yawning. It's not impressive. My cats can write that type hint with one sleepless eye open. Seriously. Why do cats sleep with one eye open, anyway? Doesn't that kinda defeat the purpose of... I dunno, sleeping? Must suck to be a paranoid cat. Uhhh. Back to the discussion.
Some type hints, however, are non-trivial. You can't write them. Nobody can. They contain more square brackets than the final dungeon of 80's ASCII BOFH terminal darling Rogue. When your type hint looks like this, the end of code maintainability cannot be far:
<sup>that smiling face is you hammering the commit button despite CI failures from @beartype.</sup>
Moreover, you don't even know the internal structure of most objects. Somebody else wrote those objects. They forgot how those objects worked a hot minute after clocking out at 4:12AM seven days deep into a crunch-time death march last January. They documented how those objects worked, but their documentation doesn't make sense and lies about everything. Now, nobody knows how those objects work.
But what if somebody did know how those objects work? What if somebody knew Python better than Python knew itself? Introducing... that somebody.
infer_hint(): Deep Introspection for the Deep Code DiverLet @beartype ease your weary burden, traveller. It is dangerous to go alone:
# Crazy object you could understand. But... ain't nobody got that kinda time.
>>> from pygments.lexers import PythonLexer
>>> root_tokens = PythonLexer().tokens["root"]
# I've got a crazy object here, @beartype. What's the crazy type hint that
# matches my crazy object? This is gonna really suck. I can *FEEL* it coming
# through my monitor tonight.
>>> from beartype.door import infer_hint
>>> infer_hint(root_tokens)
list[ # <-- what could possibly go wrong?
typing.Union[ # <-- sucky stuff starts
tuple[str | collections.abc.Callable[typing.Concatenate[object, object, ...], object], ...], # <-- sucky stuff intensifies
tuple[str | pygments.token._TokenType[str], ...], # <-- so much sucky stuff
typing.Annotated[collections.abc.Collection[str], beartype.vale.IsInstance[pygments.lexer.include]] # <-- go to heck, typing!
]
] # <-- i have no idea and neither do you
...uhh. If you say so, @beartype. I guess? </weeps_in_square_bracket_hell>
<sup>what it feels like to live in Canada, friends.</sup>
beartype.door.infer_hint() (i.e., the algorithm hereafter known simply as BeartypeAI™) knows all about deep introspection of arbitrarily complex objects. Here's what BeartypeAI™ knows:
BeartypeAI™: "I know that your type hints kinda suck. Wait... where are your type hints? I'm panicking. I'm panicking." you: "Tell my team something my team don't know, BeartypeAI™. We hate type hints. So we don't do type hints. We code instead. You should try it sometime. But... aren't you a type-checking bear? Can you even code with paws?" BeartypeAI™: "Hold my QA beer."
BeartypeAI™ is here to tell you something you don't know and wouldn't care about even if you did.
BeartypeAI™ is here to write your type hints for you. Why? Because you hate type hints. Just:
beartype.door.infer_hint() arbitrarily complex objects.It's impossible to unpack how much madness is happening inside BeartypeAI™. Let's try anyway.
<sup>son of a submariner bounces on the hot sand like bugs across your git log.</sup>
We've all been there. Some "genius"-tier wise guy devbro invented his own homebrew pure-Python collection called WeirdoCustomList without subclassing a standard collections.abc abstract base class. Sure, they could have just subclassed collections.abc.MutableSequence to write their special-needs alternative to the builtin list type. That would have been too easy. They're a masochist, so they wrote everything from scratch.
Homebrew collections are more common than you think. They're friggin' everywhere! They're multiplying like meat flies! The cat's choking on bloody homebrow collections! Look above, for example. See that nasty typing.Annotated[collections.abc.Collection[str], IsInstance[pygments.lexer.include]] type hint that BeartypeAI™ wrote for you? Yeah.
That's right. The public pygments.lexer.include type is actually a homebrew collection. They thought they were being smart. Sadly, they were being dumb. Because they wrote a homebrew collection from scratch, their collection type is unsubscriptable. You can't subscript it with child type hints like with list[str], so you can't use their collection type as a type hint factory to write type hints, so you can't actually validate their homebrew collection. In fact, you can't validate any homebrew collections.
...until now. BeartypeAI™ knows all about homebrew collections. BeartypeAI™ knows that you can actually validate homebrew collections – but only if you write a custom beartype validator leveraging the PEP 593-compliant typing.Annotated[...] type hint factory in concert with the @beartype-specific beartype.vale.IsInstance[...] validator. Please don't do this manually. You value your precious life force that is leaking all over your keyboard as we speak.
That's what the typing.Annotated[collections.abc.Collection[str], IsInstance[pygments.lexer.include]] type hint is all about. BeartypeAI™ correctly detected that this homebrew collection is actually just an instance of the pygments.lexer.include class that is externally usable as a collection of strings.
Let's get more explicit. Nobody understands pygments – not even pygments. Instead, consider...
<sup>this is for @Moosems!</sup>
It's... it's hideous!
from beartype.door import infer_hint
from collections.abc import Iterable, Iterator
# Define a weirdo custom list.
class WeirdoCustomList(object):
'''
Weirdo custom list. The one. The only.
Weirdo custom list pretends to mean no harm. Weirdo custom list is
lonely at night and only wants to be snuggle-friends. *How could you
refuse weirdo custom list in its example of need?*
'''
def __init__(self, items: list) -> None: self._items = items
def __contains__(self, item: object) -> bool: return item in self._items
def __iter__(self) -> Iterator: return iter(self._items)
def __len__(self) -> int: return len(self._items)
def __getitem__(self, index: int) -> object: return self._items[index]
def __reversed__(self) -> Iterator: return reversed(self._items)
def count(self, item: object) -> int: return self._items.count(item)
def index(self, *args, **kwargs) -> int:
return self._items.index(*args, **kwargs)
def __delitem__(self, index: int) -> None: del self._items[index]
def __setitem__(self, index: int, item: object) -> None:
self._items[index] = item
def __iadd__(self, item: object) -> object: self._items += item
def append(self, item: object) -> None: self._items.append(item)
def clear(self) -> None: self._items.clear()
def extend(self, items: Iterable) -> None: self._items.extend(items)
def insert(self, index: int, item: object) -> None:
self._items.insert(index, item)
def pop(self, *args, **kwargs) -> object:
return self._items.pop(*args, **kwargs)
def remove(self, item: object) -> None: self._items.remove(item)
def reverse(self) -> None: self._items.reverse()
# Infer the type hint for a weirdo custom list of strings.
print(infer_hint(WeirdoCustomList([
'No way,', '@beartype.', 'No.', "Friggin'.", 'Way.'])))
...which prints:
typing.Annotated[collections.abc.MutableSequence[str], IsInstance[WeirdoCustomList]]
"What's so hot about that?", you may now be thinking. Allow me to now pontificate boringly.
WeirdoCustomList isn't subscriptable. It's not a type hint factory. Moreover, despite being a mutable sequence, WeirdoCustomList doesn't actually subclass the standard collections.abc.MutableSequence protocol. Yet, @beartype correctly detected that this particular weirdo custom list is a mutable sequence of strings. How? It's best not to ask weirdo custom list these questions. :rofl:
<sup>a lone dev seeks the calm in the midst of the bug storm, yet finds only a rainbow</sup>
Callable[...] Type Hints: Gods, They Suck.Annotating callables (especially callbacks) with PEP-compliant Callable[...] type hints is basically impossible. Personally, I've never gotten a single Callable[...] type hint to work right. They never match the callables they're supposed to when I write them myself. mypy and pyright always vomit all over themselves and then me. I mostly just give up now and use the unsubscripted collections.abc.Callable abstract base class instead of full-blown Callable[...] type hints...
...until now. BeartypeAI™ knows literally everything there is to know about annotating callables. What doesn't BeartypeAI™ know? Well, friends:
BeartypeAI™ knows that a lambda function accepting no parameters is annotated as...
>>> infer_hint(lambda: "No. Friggin'. Way.")
collections.abc.Callable[[], object] # <-- woah
BeartypeAI™ knows that a lambda function accepting multiple parameters is annotated as...
>>> infer_hint(lambda you, will, believe: "@beartype, I am your code father.")
collections.abc.Callable[[object, object, object], object] # <-- i don't know what's happening here, but i like it
BeartypeAI™ knows that a normal function accepting two mandatory annotated parameters and one optional annotated parameter is "best" annotated with a PEP 612-compliant typing.Concatenate[...] subscription as...
>>> def i_am_tired(this: float, so: int, boring: str = "y u so boring, @beartype!?"): ...
>>> infer_hint(i_am_tired)
collections.abc.Callable[typing.Concatenate[float, int, ...], object] # <-- don't ask, just accept.
BeartypeAI™ knows that a decorator wrapper function accepting a PEP 612-compliant parameter specification is annotated as...
>>> from typing import ParamSpec
>>> P = ParamSpec('P')
>>> def so_param_so_spec(*args: P.args, **kwargs: P.kwargs): ...
>>> infer_hint(so_param_so_spec)
collections.abc.Callable[~P, object] # <-- yer frickin' blowin' mah mind here, yo
BeartypeAI™ knows that a decorator wrapper function accepting two mandatory annotated parameters followed by a PEP 612-compliant parameter specification is annotated with a PEP 612-compliant typing.Concatenate[...] subscription as...
>>> from typing import ParamSpec
>>> P = ParamSpec('P')
>>> def more_param_more_spec(
... go_crazy: int,
... dont_mind_if_i_do: str,
... *args: P.args,
... **kwargs: P.kwargs
... ): ...
>>> infer_hint(more_param_more_spec)
collections.abc.Callable[typing.Concatenate[int, str, ~P], object] # <-- pretty sure the universe just exploded
What I'm trying to say here is that BeartypeAI™ knows all and sees all and doesn't like it what it sees, but is still doing it's best for everybody. It knows more than me. It probably knows more than even you, even though you know everything. That's how much BeartypeAI™ knows.
<sup>@beartype casts Meteor Suplex on the runaway train that is your codebase</sup>
Tensor type hints really suck. So your team wants to annotate NumPy, JAX, PyTorch, or TensorFlow arrays, huh? That's a perfectly reasonable request. Too bad, though. Because tensor type hints suck.
Tensor type hints suck so bad you have to use third-party packages like jaxtyping just to make them work, despite the fact that both NumPy and JAX ship type hint-centric subpackages like numpy.typing and jax.typing that are supposed to make tensor type hints "just work." Of course, tensor type hints don't "just work." They don't even work...
...until now. BeartypeAI™ knows literally everything there is to know about annotating tensor type hints. Actually, that's a lie. I really wanted BeartypeAI™ to know literally everything there is to know about annotating tensor type hints in time for @beartype 0.19.0rc1. Sadly, I played video games instead. I only got around to implementing BeartypeAI™ support for inferring NumPy tensor type hints.
Still, NumPy is better than nothing. One out of four ain't bad. Right? ...anybody? :face_exhaling:
# Define the greatest NumPy array that has ever existed.
>>> from numpy import asarray
>>> best_array_is_best = asarray((1, 0, 3, 5, 2, 6, 4, 9, 2, 3, 8, 4, 1, 3, 7, 7, 5, 0,))
# Create a type hint validating that array, @beartype! Look. Just do it.
>>> from beartype.door import infer_hint
>>> infer_hint(best_array_is_best)
typing.Annotated[numpy.NDArray[int], beartype.vale.IsAttr['ndim', beartype.vale.IsEqual[1]]] # <-- wtf, @beartype
And... that's the type hint. That type hint requires no third-party dependencies. It's all @beartype, all one-liner. Nobody's writing that sort of gruelling bracket hell on their own. Not even @leycec. Now, just let somebody else do all the suffering for you. That somebody is @beartype. Who knew?
<sup>don't be that one guy who falls off the floating island. just... don't</sup> <sup></sup>
The new beartype.door.infer_hint() function tidily combines with the existing beartype.door.is_bearable() tester to create a new monstrous form of Python object test: structural similarity. But let's back up. In the beginning, there was:
# The "is" operator. Test whether two objects are literally identical.
>>> "I like big bugs and I cannot lie." is "I like big bugs and I cannot lie."
True
# The "==" operator. Test whether two objects are semantically identical.
>>> ['Other', 'devbros', 'may', 'deny',] == ['Other', 'devbros', 'may', 'deny',]
True
But what if you want to test whether two objects are structurally similar (i.e., have a similar internal structure but are neither literally nor semantically identical)? Without BeartypeAI™, you can't do that. But you have BeartypeAI™. You no longer have to accept the mouldy table scraps that the standard Python library has left you. Why? Because you have harnessed the perfidious power of beartype.door.infer_hint() + beartype.door.is_bearable(). Arise, a new darkness!
>>> from beartype.door import infer_hint, is_bearable # <-- boring stuff
# Excitement builds. Declare data structures for great glory of your code.
>>> awesome_data_structure = [{"uhh...": int, "wat!?!": [lambda: None]}, 0xFEEEEEEED]
>>> baleful_data_structure = [0xBABEEEE, {"ohgod": [lambda: True], "NOOOO!": object}]
# Describe the internal structure of your great glory.
>>> infer_hint(awesome_data_structure)
list[dict[str, type[int] | list[collections.abc.Callable[[], object]]] | int] # <-- ok
>>> infer_hint(baleful_data_structure)
list[dict[str, type[int] | list[collections.abc.Callable[[], object]]] | int] # <-- i don't know. sure, i guess?
# Do these two objects have a similar internal structure?
>>> is_bearable(baleful_data_structure, infer_hint(awesome_data_structure))
True # <----- WTF-F-F-F-F-
Structural similarity: when you care about what your objects care about.
<sup>so tired. so very, very tired.</sup>
@pablovela5620, @thiswillbeyourgithub, @WeepingClown13, @JWCS, @Logan-Pageler, @knyazer, @Moosems, @frrad, @minmax, @jaanli, @jonnyhyman, @f-fuchs, @jennydaman, @denballakh, @bionicles
Welcome. Welcome, one and all bearthonistas, to the Bear Beta Fan Club. Congrats. You're reading this, so you're automatically in the club. Everyone w
Welcome. Welcome, one and all bearthonistas, to the Bear Beta Fan Club. Congrats. You're reading this, so you're automatically in the club. Everyone who has submitted an issue in the last year has been pinged onto this announcement. If you hate @beartype or love @beartype but hate your codebase, simply unsubscribe from this announcement and watch as your codebase burns against the night sky. It's fine. "The burning means it works!", you tell your coworkers.
Beartype 0.19.0 Release Candidate 0 is the inaugural publication of @beartype's first of many painfully broken well-tested pre-releases. If you or someone you love uses @beartype, consider manually upgrading to this pre-release and reporting all painful breakage to your nearest @beartype issue tracker. Note that installing pre-release versions with pip requires manually passing the --pre option on the command-line:
pip install --upgrade --pre beartype # <-- LET THE THUNDER AND THE HEADS ROLL
You may now be thinking:
"What is this eldritch darkness in my GitHub feed?"
Allow QA-Daddy @leycec to now gently exhaust you on a Friday evening.
<sup>Bear Beta Fan Club members pass out from general excitement</sup>
@beartype 0.18.0 broke the entire world. @leycec can now admit that to himself while clutching his Maine Coon teddy cat. If your codebase survived @beartype 0.18.0, you deserve an "I Survived @beartype 0.18.0 and All I Got Was This Lousy Badge" badge. The ill-fated @beartype 0.18.0 release cycle that nearly broke my fingers taught me many things: suffering, pain, agony, blah, blah... You know. Just the standard stuff, really.
@beartype 0.18.0 taught me that @beartype has become a lot bigger than me. Other people and people-like AI that are doing meaningful things with their lives and synthetic lives (respectively) now depend on new @beartype releases not throwing up all over everybody.
@beartype ≥ 0.19.0 intends to avoid that throw-up. Several days before releasing any new major version like 0.19.0, 0.20.0, or 0.21.0: <sup>...we see the number sequence I trust</sup>
Basically, I'm just doing standard beta releases now. That's all I had to say. Instead, I laboriously enumerated a workflow that doesn't really make sense when you squint at it. Oh, well. This too was wasted time.
The sins of the fathers must never be repeated. Never forget @beartype 0.18.0! Never forgive @leycec! Wait. Shouldn't @leycec be forgiven already at some point!? <sup><-- dat poor guy</sup>
<sup>Bear Beta Fan Club members have no one to blame but @leycec</sup>
A whole lot, probably. @beartype 0.19.0:
--disable-gil compile-time option. Yes! YES! YEEEEEEESSSSS!!!! Wait. Where am I? What are these fingers on this keyboard? This must be what good dreams are typed of.--enable-experimental-jit compile-time option. OMMMMMMMMMMMMMMMMG..... It's happening. It's really happening. My breathing is now laboured and making awkwardly squishy noises that upset the cat.setuptools + setup.py <sup> :vomiting_face: </sup> to Hatch + pyproject.toml <sup> :clinking_glasses: </sup>. It went great, actually. Thanks for asking. I highly recommend Hatch for all projects – new and old. It's like Rust's Cargo, only Python. It actually works, unlike everything else. Hatch: because you're too bald to fight with Python anymore. Caveat emptor:
setuptools) at build time. If your packaging ecosystem also packages Hatch, this is trivial. Else, I sympathize with your growing toothache but can do nothing for you. Emoji man sighs. :face_exhaling:O(1) type-checks a ton of fun containers I've loosely categorized as reiterables (i.e., collections satisfying the collections.abc.Collection protocol with guaranteed O(1) read-only access to only the first collection item). Reiterables include sets, frozen sets, dictionary views, deques (i.e., double-ended queues), and all other containers matched by one or more of the following PEP 484- or 585-compliant type hints:
frozenset[...].set[...].collections.deque[...].collections.abc.Collection[...].collections.abc.KeysView[...].collections.abc.MutableSet[...].collections.abc.Set[...].collections.abc.ValuesView[...].typing.AbstractSet[...].typing.Collection[...].typing.Deque[...].typing.FrozenSet[...].typing.KeysView[...].typing.MutableSet[...].typing.Set[...].typing.ValuesView[...].@equinox.filter_jit and @jax.jit decorators. In fact, @beartype now generically supports all pseudo-callable wrapper objects (i.e., objects defining both the __call__() and __wrapped__ dunder attributes). The @beartype decorator may now be chained (i.e., listed) either below or above the third-party @equinox.filter_jit and @jax.jit decorators. Since all beartype.claw import hooks (e.g., beartype.claw.beartype_this_package()) forcefully chain @beartype above all other decorators, all @beartype import hooks now transparently support:
@equinox.filter_jit decorator.@jax.jit decorator.
<sup>Bear Beta Fan Club member after going --disable-gil + --enable-experimental-jit</sup>
Last but never least, the most important part...
This release comes courtesy these proud GitHub Sponsors, without whom @leycec's cats would currently be eating grasshoppers:
Thanks so much, masters of fintech and metrology.
<sup>The Masters of Fintech and Metrology. That's who.</sup>
Announcing the Bear Beta Fan Club from the ashes of our issue tracker:
@posita, @wesselb, @iamrecursion, @patrick-kidger, @langfield, @JelleZijlstra, @RobPasMue, @GithubCamouflaged, @kloczek, @uriyasama, @danielgafni, @JWCS, @rbroderi, @AlanCoding, @tvdboom, @crypdick, @jvesely, @komodovaran, @kaparoo, @MaximilienLC, @fleimgruber, @EtaoinWu, @alexoshin, @gabrieldemarmiesse, @James4Ever0, @NLPShenanigans, @rtbs-dev, @yurivict, @st--, @murphyk, @dosisod, @Rogdham, @alisaifee, @denisrosset, @damarro3, @ruancomelli, @jondequinor, @harshita-gupta, @jakebailey, @denballakh, @jaanli, @creatorrr, @msvensson222, @avolchek, @femtomc, @AdrienPensart, @jakelongo, @Artur-Galstyan, @ArneBachmann, @danielward27, @WeepingClown13, @rbnhd, @radomirgr, @rwiegan, @brettc, @spagdoon0411, @helderco, @paulwouters, @jamesbraza, @dcharatan, @kasium, @AdrienPensart, @sunildkumar, @peske, @mentalisttraceur, @awf, @PhilipVinc, @dcharatan, @empyrealapp, @rlkelly, @KyleKing, @skeggse, @RomainBrault
<sup>unsure what's happening here but it kinda don't seem right</sup>
The @beartype 0.18.X release cycle continues to *bear* lukewarm fruit that tastes vaguely starchy. It's cheap and smells faintly of kerosene. It keeps
The @beartype 0.18.X release cycle continues to bear lukewarm fruit that tastes vaguely starchy. It's cheap and smells faintly of kerosene. It keeps the hunger at bay even as it fails to fully satiate.
Beartype 0.18.5 preserves this trend by resolving... just a single issue! Beartype 0.18.5 resolves a critical low-level issue in @beartype's dynamic type-checking code generator for nested beartype validator-in-container type hints (e.g., type hints of the form list[typing.Annotated[{type}, Is[{validator}]]]). Someone, somewhere cares deeply about this. You might even be that someone.
Hype is completely out the window at this point, everybody. We're just hardening the fat-packed abs of the bear against critical breakage, because your codebase matters more to us than our sleep. And now...
This release comes courtesy these proud GitHub Sponsors, without whom @leycec's cats would currently be eating grasshoppers:
Thanks so much, masters of fintech and metrology.
<sup>The Masters of Fintech and Metrology. That's who.</sup>
The @beartype 0.18.X release cycle continues to *bear* lukewarm fruit that tastes vaguely starchy. It's cheap and smells faintly of kerosene. It keeps
The @beartype 0.18.X release cycle continues to bear lukewarm fruit that tastes vaguely starchy. It's cheap and smells faintly of kerosene. It keeps the hunger at bay even as it fails to fully satiate.
Beartype 0.18.4 preserves this trend by resolving... just a single issue! Beartype 0.18.4 resolves a critical low-level issue in @beartype's dynamic type-checking code generator for nested tuple-in-dictionary type hints (i.e., type hints of the form dict[tuple[...], ...]). Someone, somewhere cares deeply about this. You might even be that someone.
Hype is completely out the window at this point, everybody. We're just hardening the fat-packed abs of the bear against critical breakage, because your codebase matters more to us than our sleep. And now...
This release comes courtesy these proud GitHub Sponsors, without whom @leycec's cats would currently be eating grasshoppers:
Thanks so much, masters of fintech and metrology.
<sup>The Masters of Fintech and Metrology. That's who.</sup>
Beartype 0.18.3 is the minor patch release that your careening codebase can no longer live without:
Beartype 0.18.3 is the minor patch release that your careening codebase can no longer live without:
pip install --upgrade beartype
Actually... I lied. I know! I gotta stop doing that. But the sordid truth is that beartype 0.18.3 is mostly just for @iamrecursion and @sylvorg, who single-handedly reported more issues in a single week than the exploding size of @leycec's JRPG backlog. And we know how big that is, don't we? It's big. It's so big it wraps around like a self-sustaining Niven ringworld habitat at Lagrange point L1. Big-big.
Beartype 0.18.3 is for @iamrecursion and @sylvorg. May their usernames live forever in git log infamy. In this release, a few more bugs die.
But first...
This release comes courtesy these proud GitHub Sponsors, without whom @leycec's cats would currently be eating grasshoppers:
Thanks so much, masters of fintech and metrology.
<sup>The Masters of Fintech and Metrology. That's who.</sup>
So. Funny story. Turns out you can bound PEP 484-compliant type variables (i.e., typing.TypeVar(...) objects) with forward references specified as strings. Who knew? Everybody except @leycec. Nobody tells that guy nuthin'.
Beartype 0.18.3 now explicitly supports type variables bound by forward references. These are type hints of the form:
TypeVar('{TypeVarName}', bounds='{UndefinedType}')
Previously, @beartype only partially supported such variables due to @leycec failing to realize that such variables even existed and constituted a valid use case. This is why your codebase can't have good things. Now, @beartype fully supports heinous abominations valid use cases like:
from beartype import beartype
from typing import TypeVar
# Type variable bound by a forward reference. @beartype supports your
# weird stuff, because normalcy is just a null pointer to garbage.
Fuggit = TypeVar('Fuggit', bound='EnduringMisery')
@beartype
class EnduringMisery(object):
def searing_pain(self) -> tuple[Fuggit, Fuggit]:
return ('Fuggit...', '...up!')
# Can anyone guess what this does? That's right. It blows up. Fuggit!
blinding_agony = EnduringMisery()
blinding_agony.searing_pain()
<sup>hangin' with the bear homies in apocalyptic wasteland ain't no thang</sup>
beartype.vale.Is[...]: Now It Supports Crazy StuffThe functional beartype validator factory beartype.vale.Is[...] is now subscriptable (indexable) by all manner of shambolic nightmares. Previously, you had to subscript Is[...] with low-level functions and methods. Now, you can subscript Is[...] with high-level callable objects like:
Class-based callables (i.e., objects whose classes define the __call__() dunder method, rendering otherwise uncallable objects callable): e.g.,
from beartype.door import is_bearable
from beartype.typing import Annotated
from beartype.vale import Is
from functools import partial
class TruthSeeker(object):
def __call__(self, obj: object) -> bool:
'''
Tester method returning :data:`True` only if the passed object
evaluates to :data:`True` when coerced into a boolean and whose
first parameter is ignorable.
'''
return bool(obj)
# Beartype validator matching only objects that evaluate to "True".
Truthy = Annotated[object, Is[TruthSeeker()]]
assert is_bearable('', Truthy) is False
assert is_bearable('Even lies are true now, huh?', Truthy) is True
Partials (i.e., high-level functools.partial(...) callable objects wrapping low-level functions and methods): e.g.,
from beartype.door import is_bearable
from beartype.typing import Annotated
from beartype.vale import Is
from functools import partial
def is_true(ignorable_arg, obj):
'''
Tester function returning :data:`True` only if the passed object
evaluates to :data:`True` when coerced into a boolean and whose first
parameter is ignorable.
'''
return bool(obj)
# Partial of the is_true() tester defined above, effectively ignoring the
# "ignorable_arg" parameter accepted by that tester.
is_true_partial = partial(is_true, 'Gods. This code is literally unreadable.')
# Beartype validator matching only objects that evaluate to "True".
Truthy = Annotated[object, Is[is_true_partial]]
assert is_bearable('', Truthy) is False
assert is_bearable('Even lies are true now, huh?', Truthy) is True
Is this valuable? No idea. Let's pretend I did something useful tonight so I can sleep without self-recrimination.
<sup>...heh. your eyes are now bleeding</sup>
Beartype 0.18.3 now sports improved support <sup>we rhymin' like it's 2099 ova here</sup> for Jupyter Notebook cells. Do you like Jupyter? Do you like @beartype? Then you need beartype 0.18.3 now, because beartype 0.18.2 probably already broke everything without your informed consent. Woops.
Beartype 0.18.3 resolves inscrutable non-determinism (which is technically deterministic if you squint at it, but we don't talk about that) with respect to repeatedly redefined classes defining one or more methods annotated by one or more self-referential relative forward reference (i.e., referring to the class currently being defined). @beartype is now considerably more robust against non-determinism in Jupyter cells containing @beartype-decorated self-referential classes like:
from beartype import beartype
@beartype
class MuhSelfReferentialClass(object):
def __init__(self, muh_var: int) -> None:
self.muh_var = muh_var
@classmethod
def muh_factory(cls, muh_var: int) -> "MuhSelfReferentialClass":
'''
This is fine now. No matter how much you reload the cell
defining this class, @beartype will still stan for you.
I have no idea what "stan" even means. I think it's good.
'''
return MuhSelfReferentialClass(muh_var + 42)
muh_object = MuhSelfReferentialClass.muh_factory(42)
Flex those burly QA biceps, @beartype. Flex 'em.
<sup>things explode when you put @beartype back in the sheath</sup>
__class_getitem__ = classmethod(GenericAlias): We Do That Too, Whatever That IsSo. You want to refactor your heroic class that will truly shape the course of human history into a subscriptable type hint factory. You even know about the convenient but unreadable one-line idiom for casting this dark magic. Previously, @beartype refused to support your bad habits arcane knowledge. Now, @beartype understands and appreciates everything you're trying to do for humanity.
Beartype 0.18.3 generalizes the @beartype decorator to support decoration of user-defined types that declare class methods by directly calling the builtin @classmethod decorator as a function passed a C-based callable type (e.g., classmethod(types.GenericAlias)). Doing so enables @beartype to support the standard idiom for user-defined subscriptable type hint factories under Python >= 3.9:
from abc import ABCMeta
from beartype import beartype
from types import GenericAlias
@beartype
class MuhTypeHintFactory(metaclass=ABCMeta):
'''
Congrats. Subscripting this class now trivially makes new type hints
that @beartype fails to understand or appreciate.
'''
# This exact one liner appears verbatim throughout the standard
# library as well as popular third-party packages like NumPy.
__class_getitem__ = classmethod(GenericAlias)
# Not sure what this means, but you insist you know what you're doing.
# *Do* you, though? *Do* you? @beartype is out to lunch on this one.
MuhTypeHint = MuhTypeHintFactory[str]
<sup>the pancakes get me every time. srsly. what is with those pancakes?</sup>
Beartype 0.18.3 deprioritizes @beartype-specific forward reference proxies (i.e., internal objects proxying external user-defined types that have yet to be defined) in type tuples passed as the second arguments to the isinstance() builtin, reducing the likelihood that type-checks involving forward references will raise unexpected exceptions. For example, consider this simple example:
from beartype import beartype
from beartype.typing import Union
@beartype
def explosive_funk(muh_arg: Union['UndefinedType', None] = None):
print("You thought this was gonna blow up, huh? You're not alone.")
print("Unless you're in space. In which case you're really alone.")
explosive_funk()
class UndefinedType(object): ...
...which unexpectedly prints without blowing up:
You thought this was gonna blow up, huh? You're not alone.
Unless you're in space. In which case you're really alone.
@beartype type-checks that the default value of the optional muh_arg parameter of the muh_func() function satisfies the type hint 'UndefinedType' | None – despite the fact that the UndefinedType class is undefined! To do so, @beartype now internally reorders the types comprising this union:
# ...from this default type-check, which would raise a decoration-time
# exception due to "UndefinedType" being undefined...
isinstance(muh_arg, (UndefinedTypeProxy, NoneType))
# ...to this default type-check, which should raise *NO* decoration-time
# exception. Why? Because the default value "None" for the "muh_arg"
# parameter satisfies the first "NoneType" type, which then
# short-circuits the isinstance() call and thus ignores the problematic
# "UndefinedTypeProxy" type altogether.
isinstance(muh_arg, (NoneType, UndefinedTypeProxy))
Nobody should ever depend upon this. Therefore, this is a delicious nothingburger – but a delicious nothingburger that could yield future delights in the event that we actually elect to try type-checking defaults at decoration time again. We're not, of course. That would be foolish and dangerous. <sup>We're absolutely going to do that again.</sup>
<sup>rub those cat cheeks! rub 'em!</sup>
Beartype 0.18.3 resolves a subtle interaction between PEP 563 (i.e., from __future__ import annotations), PEP 673 (i.e., typing{_extension}.Self), and common dunder methods like... uh, __add__(), I guess. Let's pretend that's common.
Beartype 0.18.3 ensures that the type stack encapsulating the current @beartype-decorated class is now preserved throughout the type-checking process for standard dunder methods annotated by one or more PEP 673-compliant typing{_extension}.Self type hints that are stringified under PEP 563. For example, @beartype now transparently supports pernicious edge cases resembling:
from beartype import beartype
from typing_extensions import Self
@beartype
class MyClass:
attribute: int
def __init__(self, attr: int) -> None:
self.attribute = attr
def __add__(self, other: int) -> Self:
self.__class__(self.attribute + other)
If you wanted this, you are literally @iamrecursion. Congrats.
<sup>you too will believe that @beartype 0.18.3 actually works</sup>
Pinging @posita, @iamrecursion, @sylvorg, @tactile-metrology, @kalaspuff, @danielward27, @kloczek, @uriyasama, @danielgafni, @JWCS, @rbroderi, @AlanCoding, @tvdboom, @crypdick, @WeepingClown13, @RobPasMue, @rbnhd, @radomirgr, @rbroderi.
You are wanted on floor 13. Japanese buildings don't even have a floor 13. Surely nothing could go wrong by violating that fundamental.
<sup>those dance moves can mean only one thing... This was @beartype 0.18.3.</sup>
Much like your neighbour's obese cat, even @beartype 0.18.1 didn't quite work out as expected. This patch release temporarily squelches (i.e., silence
Much like your neighbour's obese cat, even @beartype 0.18.1 didn't quite work out as expected. This patch release temporarily squelches (i.e., silences) a low-level assert statement erroneously performed during @beartype's dynamic code generation loop. Is this the end to the horror show that keeps on giving? Will the ill-fated @beartype 0.18.x release cycle finally pass muster and stop violating the world? Tune in next time as @leycec clutches his chest in agony live on GitHub.
Obligatory awesome peeps!
This release comes courtesy these proud GitHub Sponsors, without whom @leycec's cats would currently be eating grasshoppers:
Thanks so much, masters of fintech.
<sup>The Masters of Fintech. That's who.</sup>
Much like your neighbour's obese cat, @beartype 0.18.0 didn't quite work out as expected. This patch release temporarily reverts default value type-ch
Much like your neighbour's obese cat, @beartype 0.18.0 didn't quite work out as expected. This patch release temporarily reverts default value type-checking (i.e., type-checking of default values of optional parameters annotated by type hints accepted by @beartype-decorated callables), restoring sanity to the downstream community of @beartype consumers.
Although desirable, this functionality is also a lot more nuanced than @leycec previously assumed -- resulting in @leycec clutching his bald head as if he still had something to clutch there. Since improving this functionality to be robust against breakage is non-trivial, the proper solution is to temporarily disable that functionality. Thus, we cry.
Obligatory awesome peeps!
This release comes courtesy these proud GitHub Sponsors, without whom @leycec's cats would currently be eating grasshoppers:
Thanks so much, masters of fintech.
<sup>The Masters of Fintech. That's who.</sup>
Does anyone even care about deprecations anymore? *Everything* in the standard typing package has now been deprecated. When everything is deprecated,…
Beartype 0.18.0 whimsically gyrates around in balloon pants while chanting: "Can't touch this."
<sup>...even the token bald man in a smoking post-apocalyptic wasteland doesn't know</sup>
It's kinda creepy, honestly – and it's really getting on your nerves. Yes, we get it. You think you're hot junk, @beartype 0.18.0! Yet you can't help but admire the sweat that coats its body. Beartype 0.18.0 worked out for this release. It's buff now. It's tough now. It does things now. It does things you thought it did five years ago now. Beartype 0.18.0 finally caught up to your expectations. It even tried to exceed them. It didn't, of course. It couldn't. It's only a bear. It rarely bathes. And, anyway, your expectations were unreasonable.
But... it almost did. Prepare to have your expectations almost exceeded:
pip install --upgrade beartype
But what is @beartype 0.18.0? What does it do? Nuthing, huh? It's all just hollow hype and empty promises again, huh?
To answer that reasonable question, let's unreasonably back up with an extended monologue while the camera man slow pans across @leycec's baldpate. What was @beartype < 0.18.0? Why would anyone actually suffer install older @beartype releases? In the anachronistic words of an inconvenient acronym I just made up: scientific quality assurance (SciQA). So you wanna type-check...
jaxtyping. :kissing_heart:pandera. :hugs:typing oblivion. :smiling_face_with_tear:We're agreed that @beartype < 0.18.0 was limited in scope. If you fit inside that scope, your codebase fits inside a backpack. Congratulations. It pays to be lean. But what if you have a real codebase? What if you wanted to actually type-check general-purpose Python containers outside that scope?
You use @beartype 0.18.0! That's right. We're finally type-checking general-purpose Python containers. But first...
Prepare your battle-hardened body and soul for the epic maelstrom of delivered features that follows by watching this malicious YouTube video! Just kidding. It's wholesome. Really. It's Saitama vs. Genos – surely humanity's crowning achievement. Praise be to Arifumi Imai for he has seen the countenance of many small gods and found them all sadly lacking.
@leycec always queues up Saitama vs. Genos when he needs to get hyped. Gonna groom the hair off a scary cat giving you the ugly fish-eyed thousand-yard death grimace? Saitama vs. Genos. Gonna remount your girlfriend's 64-core ThreadInfernoBurner CPU that's already sintered six motherboards into charred thermal paste in the wood shed out back that the sea walruses are rifling through? Saitama vs. Genos. When things get real, you just get realer. Saitama vs. Genos.
<sup>when your shoulders rip off their hinges, you just hope that t-shirt was disposable</sup>
And now...
This release comes courtesy these proud GitHub Sponsors, without whom @leycec's cats would currently be eating grasshoppers:
Thanks so much, masters of fintech.
<sup>The Masters of Fintech. That's who.</sup>
And now... the moment people I have never met have been waiting for.
Beartype 0.18.0 now deeply type-checks the first key-value pair of each dictionary (mapping) annotated by a dictionary (mapping) type hint in O(1) constant time with negligible constant factors. This means near-real-time with runtime overhead of at most ~1µs (i.e., one microsecond, one millionth of a second) per type-check. This includes all hints of the form:
dict[..., ...].collections.defaultdict[..., ...].collections.abc.Mapping[..., ...].collections.abc.MutableMapping[..., ...].collections.abc.OrderedDict[..., ...].typing.DefaultDict[..., ...].typing.Dict[..., ...].typing.Mapping[..., ...].typing.MutableMapping[..., ...].typing.OrderedDict[..., ...].This (...probably) also includes @wesselb's multiple-dispatch pièce de résistance Plum, which should now automatically multiple-dispatch across different kinds of dictionaries without @wesselb actually having to do anything. Let us choose to believe this optimistic prophecy I have delivered.
Beartype 0.18.0 does so (...effectively) recursively on arbitrarily nested combinations and permutations of those type hints. The proof is in the disgusting British blood pudding possibly named something like "toad-in-the-bear-hole":
from beartype import beartype
from collections.abc import Mapping, MutableMapping
@beartype
def go_bear(bear_bros_for_life: dict[
int, Mapping[str, MutableMapping[bytes, bool]]]) -> None:
print(bear_bros_for_life)
# This passes. A beautiful dictionary brings a tear to the eye.
go_bear({
1: {
'Beautiful bird;': {
b'thou voyagest to thine home,': False,
},
},
})
# This fails! A horrible dictionary brings your app crashing to the ground.
go_bear({
1: {
'With thine,': {
b'and welcome thy return with eyes': 1,
},
},
})
Type-checking violation messages even identify the exact key-value pair of arbitrarily complex pure-Python data structures responsible for those violations. The above example now helps you ruin your coworker's all-too-brief web app career by raising:
beartype.roar.BeartypeCallHintParamViolation: Function __main__.go_bear()
parameter bear_bros_for_life={1: {'With thine,': {b'and welcome thy return with eyes': 1}}}
violates type hint dict[int, collections.abc.Mapping[str, collections.abc.MutableMapping[bytes, bool]]],
as dict key int 1 value dict key str 'With thine,' value dict key bytes
b'and welcome thy return with eyes' value int 1 not instance of bool.
Beartype 0.18.0: I swear that looks more readable when you see it in person.
<sup>if my leg ever bends like that, please call for help</sup>
...heh. So, funny story. Apparently, @beartype < 0.18.0 didn't bother type-checking the default values of optional parameters at early @beartype decoration time. Why even bother, right? Beartype < 0.18.0 trusted you against its better judgement. Beartype < 0.18.0 only type-checked the default values of unpassed optional parameters at late function call time; if you always passed optional parameters (or never even called functions that accept optional parameters), @beartype < 0.18.0 never type-checked their defaults. This is why your office luncheons always order take-out that tastes like plastic.
Beartype 0.18.0 conveniently overlooks the abject failings of the distant past by embracing a new normal that you always thought was happening. Now, it is. All default values are now type-checked at early @beartype decoration time – with one prominent exception we're about to get to.
Behold! Type-check defaults at decoration time or go home, @beartype 0.18.0:
from beartype import beartype
@beartype
def beartype_i_am_your_code_father(
nooooooooooooo: int = 'Oh, you will be. You will be.') -> None: ...
Despite the offending (and clearly offensive) beartype_i_am_your_code_father() function not being called, the @beartype decorator now raises the expected type-checking violation at decoration time:
beartype.roar.BeartypeDecorHintParamDefaultViolation: Function
__main__.beartype_i_am_your_code_father() parameter "nooooooooooooo"
default value 'Oh, you will be. You will be.' violates type hint
<class 'int'>, as str 'Oh, you will be. You will be.' not instance of int.
Above, we wrote that:
All default values are now type-checked at early
@beartypedecoration time – with one prominent exception we're about to get to.
What "prominent exception," @beartype? What bald-faced lies are you trying to sell us now, @beartype?!
The prominent exception is forward references. When the type hint annotating an optional parameter contains one or more unresolvable forward references (i.e., references to types that have yet to be defined), the @beartype decorator just issues a non-fatal warning rather than raising a fatal exception. After all, there might be a real problem there – but there might also not be a real problem there. @beartype can't tell, because the type is undefined. So, @beartype just notifies you that something is up. The power is in your hands. The power was always in your hands. After all, you use @beartype: the QA Power Glove.™ <sup>← awkwardly dated 80's moments</sup>
Arise! Ignore unresolvable forward references in optional parameters at decoration time, beartype 0.18.0:
from beartype import beartype
@beartype
def ive_seen_bugs(you_people_wouldnt_believe: 'TearsInTheGitter' = (
'Attack one-liners on fire off the shoulder of GitHub.')) -> None: ...
...which merely issues this non-fatal warning:
BeartypeDecorHintParamDefaultForwardRefWarning: Function
__main__.ive_seen_bugs() parameter "you_people_wouldnt_believe"
default value 'Attack one-liners on fire off the shoulder of GitHub.'
uncheckable at @beartype decoration time, as forward reference
"TearsInTheGitter" unimportable from module "__main__".
@beartype
Beartype 0.18.0: because QA in 2024 is so complicated that you're just passively nodding along in the vain hope that one of these nothingburgers will start making sense.
<sup>if you ever see the Japanese character for death suspended in the air outlined in red letters, @beartype probably can't help you anymore. still, it's worth a try</sup>
typing.TypeGuard + beartype.door.is_bearable(): It's BAAAAAAAAAACKBeartype 0.18.0 resuscitates PEP 647-compliant typing.TypeGuard[T]-based type narrowing on the beartype.door.is_bearable() type-checker. This means that mypy now loves beartype.door.is_bearable() as much as you love your rabid scrappy dog that mauls the jaded postal worker every day:
from beartype.door import is_bearable # <-- *NOW WITH THE POWER OF PEP 647*
def narrow_types_like_a_boss_with_beartype(lst: list[int | str]):
if is_bearable(lst, list[int]):
munch_on_list_of_integers(lst) # <-- mypy now loooves this
elif is_bearable(lst, list[str]):
munch_on_list_of_strings(lst) # <-- mypy love intensifies
def munch_on_list_of_strings(lst: list[str]): ...
def munch_on_list_of_integers(lst: list[int]): ...
This is entirely thanks to a mammoth dissertation-length dissection by Python's unassailable typing genius @asford (Alex Ford) on the intersection <sup>oh gods what does any of this mean anymore</sup> of runtime and static type-checking vis-a-vis the procedural statement-level hybrid runtime-static type-checker beartype.door.is_bearable(), the PEP 484-compliant @typing.overload decorator, and the PEP 647-compliant typing.TypeGuard[T] type hint. Please redirect all blame towards @asford. I barely understand anything anymore.
Sadly:
This does not extend to the comparable object-oriented beartype.door.TypeHint.is_bearable() method – which continues to not perform type narrowing. Due to deficiencies in @leycec's wobbly brain, only the procedural beartype.door.is_bearable() function currently performs type narrowing.
This may not extend to pyright. Although mypy appears to fully support this API, pyright appears to raise fatal errors that make no sense and suggest pyright has an equally wobbly brain:
/home/leycec/py/beartype/beartype/door/_doorcheck.py
/home/leycec/py/beartype/beartype/door/_doorcheck.py:209:5 - error: Overloaded implementation is not consistent with s>
Function return type "TypeGuard[T@is_bearable]" is incompatible with type "bool"
"TypeGuard[T@is_bearable]" is incompatible with "bool" (reportInconsistentOverload)
/home/leycec/py/beartype/beartype/door/_doorcheck.py:209:5 - error: Overloaded implementation is not consistent with s>
Function return type "TypeGuard[T@is_bearable]" is incompatible with type "bool"
"TypeGuard[T@is_bearable]" is incompatible with "bool" (reportInconsistentOverload)
2 errors, 0 warnings, 0 informations
What's "funny" about that is that:
All TypeGuard[...] type hints reduce to and are thus compatible with the standard bool type.
PEP 647 claims that the reference implementation of PEP 647 is (...waitforit) pyright:
The Pyright type checker supports the behavior described in this PEP.
Guess it doesn't, huh? We cry wet crocodile tears for OO and pyright.
Beartype 0.18.0: @leycec cannot be held responsible for his own failings.
<sup>@leycec clutches a living-preserving cup of "matcha on the rocks."</sup>
typing.TypeAlias: It's Deprecated, But That's Okay, Because Literally Everything Is DeprecatedDoes anyone even care about deprecations anymore? Everything in the standard typing package has now been deprecated. When everything is deprecated, nothing is deprecated. The desensitization is real and you no longer care.
Beartype 0.18.0 appreciates your growing sense of futility and vaguely uneasy apprehension of impending doom. After all, @beartype is the codebase built by a playlist of twelve continuous days of stoner caveman doom metal from Dorset. If it's got a reputable name like "Dopesmoker", "Dopethrone", "Shroomaroom", or "Satori", we probably resolved your issue to it without your consent. Bonus GitHub karma to the bear bro that names the album that starts with this well-intended life lesson:
Drop out of life with bong in hand! Follow the smoke to the riff-filled land!
That's why @beartype now officially supports PEPs that are dead that nobody cares about anymore like PEP 613: typing.TypeAlias. Although deprecated by PEP 695 type aliases (e.g., type hints of the form type {alias_name} = {alias_value} under Python ≥ 3.12), PEP 613 type aliases are still widely prevalent throughout the open-source community. Specifically, @beartype now:
Emits an absurd deprecating warning for each PEP 613 type alias that spans five volumes of archaic dead-tree print like that long-lost Brandon Sanderson cyberpunk fantasy saga you always knew existed:
BeartypeDecorHintPep613DeprecationWarning: PEP 613 type hint
typing.TypeAlias deprecated by PEP 695. Consider either:
* Requiring Python >= 3.12 and refactoring PEP 613 type aliases into
PEP 695 type aliases. Note that Python < 3.12 will hate you for
this: e.g.,
# Instead of this...
from typing import TypeAlias
alias_name: TypeAlias = alias_value
# ...just do this.
type alias_name = alias_value
* Refactoring PEP 613 type aliases into PEP 484 "typing.NewType"-based
type aliases. Note that static type-checkers (e.g., mypy, pyright,
Pyre) will hate you for this: e.g.,
# Instead of this...
from typing import TypeAlias
alias_name: TypeAlias = alias_value
# ...just do this.
from typing import NewType
alias_name = NewType("alias_name", alias_value)
Combine the above two approaches via The Ultimate Type Alias (TUTA),
a hidden ninja technique that supports all Python versions and static
type-checkers but may cause coworker heads to pop off like in that one
jolly Kingsman scene:
# Instead of this...
from typing import TypeAlias
alias_name: TypeAlias = alias_value
# ..."just" do this. If you think this sucks, know that you are not alone.
from typing import TYPE_CHECKING, NewType, TypeAlias # <-- sus af
from sys import version_info # <-- code just got real
if TYPE_CHECKING: # <-- if static type-checking, then PEP 613
alias_name: TypeAlias = alias_value # <-- grimdark coding style
elif version_info >= (3, 12): # <-- if Python >= 3.12, then PEP 695
exec("type alias_name = alias_value") # <-- eldritch abomination
else: # <-- if Python < 3.12, then PEP 484
alias_name = NewType("alias_name", alias_value) # <-- coworker gives up here
Otherwise ignores each PEP 613 type alias, which conveys no meaningful semantics or metadata. Frankly, it's unclear why PEP 613 even exists. The CPython developer community felt similarly, which is why PEP 695 type aliases deprecate PEP 613.
Beartype 0.18.0: it's not our fault.
<sup>gettin' kinda queasy watchin' cyborg man spin around like an unsafe carousel on fire</sup>
They still blow chunks, of course. @beartype gonna @beartype. But at least warning and exception messages emitted by @beartype no longer blow quite as much chunks. I'll take it.
Specifically:
Type-checking violations now sport a (slightly) more coherent colour scheme. Let me know if you hate it. Monochrome lovers gonna hate, of course. I (probably) won't do anything about your justifiable complaints, of course. I'm unresponsive, mentally flabby, and lack fortitude. I'll just quietly accept your abuse. But at least one of us might feel better.
The beartype.door.beartype_this_package() import hook (i.e., the rising child star of the @beartype ecosystem that our sketchy PR agency has been spamming your inbox with) now raises exception messages like this when called from:
Top-level scripts:
beartype.roar.BeartypeClawHookUnpackagedException: Top-level script
"/home/leycec/tmp/src/script.py" resides outside package structure.
Consider calling another "beartype.claw" import hook. However, note that only
other modules will be type-checked. "/home/leycec/tmp/src/script.py" itself
will remain unchecked. All business logic should reside in submodules
subsequently imported by "/home/leycec/tmp/src/script.py": e.g.,
# Instead of this at the top of "/home/leycec/tmp/src/script.py"...
from beartype.claw import beartype_this_package # <-- you are here
beartype_this_package() # <-- feels bad
# ...pass the basename of the "src/" subdirectory explicitly.
from beartype.claw import beartype_package # <-- you want to be here
beartype_package("src") # <-- feels good
from src.main_submodule import main_func # <-- still feels good
main_func() # <-- *GOOD*! "beartype.claw" type-checks this
some_global: str = 0xFEEDFACE # <-- *BAD*! "beartype.claw" ignores this
This has been a message from your friendly neighbourhood bear.
Top-level modules:
Top-level module "main.py" resides outside package structure but was
*NOT* directly run as a script. "beartype.claw" import hooks require
that modules either reside inside a package structure or be directly
run as scripts. Since neither applies here, you are now off the deep
end. @beartype no longer has any idea what is going on, sadly.
Consider directly decorating classes and functions by the
@beartype.beartype decorator instead: e.g.,
# Instead of this at the top of "main"...
from beartype.claw import beartype_this_package # <-- you are here
beartype_this_package() # <-- feels bad
# ...go old-school like it's 2017 and you just don't care.
from beartype import beartype # <-- you want to be here
@beartype # <-- feels good, yet kinda icky at same time
def spicy_func() -> str: ... # <-- *GOOD*! @beartype type-checks this
some_global: str = 0xFEEDFACE # <-- *BAD*! @beartype ignores this, but what can you do
For your safety, @beartype will now crash and burn.
Prefixes all warning messages with contextual metadata describing the origin of those warnings. This includes the names of the responsible module, class, and/or callables that are rapidly sapping your will to code even a single line more. Notably:
PEP 585 deprecations, in particular, were particularly useless. They failed to notify you where exactly the deprecated type hint came from. Now, @beartype candidly tells all about childhood trauma on the Christopher Robin farm:
from beartype import beartype
from typing import List # <-- deprecated is bad, but that doesn't mean you care
@beartype
def when_the_going_get_buggy(the_buggy_get_beartype: List[str]) -> None: ...
...which now raises a deprecation warning that actually helps you succeed on your own merit for once:
BeartypeDecorHintPep585DeprecationWarning: Function
__main__.when_the_going_get_buggy() parameter "the_buggy_get_beartype"
PEP 484 type hint typing.List[str] deprecated by PEP 585. This hint is
scheduled for removal in the first Python version released after October
5th, 2025. To resolve this, import this hint from "beartype.typing"
rather than "typing". For further commentary and alternatives, see also:
https://beartype.readthedocs.io/en/latest/api_roar/#pep-585-deprecations
PEP 526-compliant annotated variable assignments (e.g., muh_var: int | str = True), for which beartype.claw import hooks raised similarly useless type-checking violation messages. Thankfully:
Global annotated variable assignments like this:
bad_global: str = 0xFEEDFACE
...now yield violations like this:
beartype.roar.BeartypeDoorHintViolation: Global variable
"__main__.bad_global" value 4277009102 violates type hint <class
'str'>, as int 4277009102 not instance of str.
Local annotated variable assignments like this:
def bad_func():
bad_local: int = "This local is bad. It's bad. It knows it."
bad_func()
...now yield violations like this:
beartype.roar.BeartypeDoorHintViolation: Callable
paga.__main__.bad_func() local variable "bad_local" value "This local
is bad. It's bad. It knows it." violates type hint <class 'int'>, as
str "This local is bad. It's bad. It knows it." not instance of int.
Beartype 0.18.0: raise your paws in the air like you just don't care.
<sup>that feeling when you replace your hand with a black-and-white Ultima Cannon and it's still not good enough</sup>
Beartype 0.18.0 salutes the bear bros and gals and cats and rabid scrappy dogs that made this miracle possible:
@posita, @patrick-kidger, @wesselb, @tusharsadhwani, @JWCS, @iamrecursion, @rbroderi, @tvdboom, @AlanCoding, @crypdick,<sup>← lol</sup> @komodovaran, @rwiegan, @avolchek, @jaanli, @brettc, @spagdoon0411, @helderco, @jamesbraza, @dcharatan, @kasium, @uriyasama
<sup>Standard @beartype training exercise: all fun and games until you're down to the last chip.</sup>
Beartype 0.17.2 nervously skitters about on thin ice. Cracks form, yet beartype 0.17.2 fails to return to shore. "What are you even doing!?", the crow
Beartype 0.17.2 nervously skitters about on thin ice. Cracks form, yet beartype 0.17.2 fails to return to shore. "What are you even doing!?", the crowd exclaims. Verily, it is best not to ask questions:
pip install --upgrade beartype
This patch release comes courtesy these proud GitHub Sponsors, without whom @leycec's cats would currently be eating grasshoppers:
Thanks so much, masters of fintech.
Alright, alright. You found us out already.
Beartype 0.17.2 is an extremely minor patch release that exists purely to relax the bad assumption that all Python 3.9 releases unconditionally define the standard typing.ForwardRef.__forward_module__ dunder attribute, resolving issue #324 kindly submitted by stone-cold typonista @jvesely (Jan Vesely). Although Python ≥ 3.9.18 definitively defines this attribute, an unknown range of older Python 3.9 patch releases fail to do so.
Beartype 0.17.2 resolves this by naively pretending that all Python 3.9 releases fail to do so. Although kinda non-ideal, it's unclear whether this attribute is even used (i.e., set to a string) under Python 3.9. In fact, it's unclear whether this attribute is even used anywhere, ever. It probably will be under Python ≥ 3.13, but that's putting the proverbial cart before the horse. Anyyyyyyway.
We now return to your regularly scheduled Python hackathon.
Beartype 0.17.1 gently descends from the heavens on a golden dragon made of rainbows. "How can this be!?", the crowd exclaims. Verily, it is best not
Beartype 0.17.1 gently descends from the heavens on a golden dragon made of rainbows. "How can this be!?", the crowd exclaims. Verily, it is best not to ask questions:
pip install --upgrade beartype
This patch release comes courtesy these proud GitHub Sponsors, without whom @leycec's cats would currently be eating grasshoppers:
Thanks so much, masters of fintech.
This patch release adds explicit support for typing.NamedTuple subclasses under PEP 563 (i.e., from __future__ import annotations), resolving issue #318 kindly submitted by the cosmically rare-earth GitHub element @kasium. For unknown reasons (which probably reduce to "Guido was tired that day."), the typing.NamedTuple superclass exhibits high strangeness.
Specifically, for each typing.NamedTuple subclass named {MuhTuple}, the typing.NamedTuple superclass dynamically generates a {MuhTuple}.__new__() dunder method whose:
__annotations__ dunder attribute wraps all stringified type hints inside typing.ForwardRef(...) objects. Why? No reason.__module__ dunder attribute claims that method was defined in a fake module named named_{MuhTuple}. Why? Nobody knows.For example:
from __future__ import annotations # <-- PEP 563: it makes kittens cry
import typing
# When you said this...
class MuhTuple(typing.NamedTuple): # <-- very reasonable
muh_field: int # <-- makes sense, huh?
# ...what "typing.NamedTuple" heard you say was this:
class MuhTuple(typing.NamedTuple):
def __new__(cls, muh_field: ForwardRef('int')) -> None: # <-- wut
self.muh_field = muh_field
MuhTuple.__new__.__module__ = 'named_MuhTuple' # <-- lolbro
Why does typing.NamedTuple do these sad things? Because it is crazy. This is the official answer. Cray classes gonna cray.
@beartype now responds with a mountain of code that took us two weeks. Was that worth it? Probably not. Probably should have just implemented deep type-checking already. Instead, this is all we got.
@beartype 0.17.0 advised everybody to donate money to charitable causes. Instead, everybody donated more money to @beartype. Reverse psychology surely is the path of righteousness.
These monocled code aristocrats graciously filled the money trough, which the cats are now sleeping on against our wishes:
@DylanModesitt. @beartype now proudly markets you and everything you do. We're Team @DylanModesitt over here. Also, your GitHub Avatar is the boss. Feast your eyes, everybody!
<sup>the eyes follow my fingers as i type my password</sup>
@zhiyuanshi. Thanks to you, I have money. More importantly, I now know about spacemacs: Emacs + Vim key bindings. Urge... to... switch... IDEs... rising. My danger sense is tingling.
Lastly, @beartype thanks Bully Maguire for saving New York City with sassy hair, emo eye shadow, and impromptu cafe street dancing.
<sup>@beartype make a man feel like this, sometimes</sup>
…over 20 severe issues, and emits 7 severe NumPy deprecation warnings. nptyping is a ticking time bomb about to explode your codebase into radiating bl…
Team Tokyo Bear presents... Ultrabear vs. Mecha-Bugbear, the titanic struggle of ultimate opposites. On the left, @beartype 0.17.0 in the hybrid static-runtime type-checking corner. On the right, the voracious bugs proliferating throughout your codebase in adorable collectable card format.
vs. <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.
@beartype 0.17.0 is a-go-go:
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:
<sup>when you're straddling a giant fish head in the canadian rockies and conehead sumo baby just wanna play</sup>
But first...
We give thanks. I'm humbly and hugely grateful to everyone who's ever financially supported @beartype via GitHub Sponsors. I'm especially grateful to our generous lifetime donors who almost gave a kidney for @beartype. These are @beartype's Three Biggest Fat Bear-cats:
jaxtyping support? We do that. Equinox support? That too. If it's a @patrick-kidger byproduct, @beartype probably now hawks it on Etsy.@beartype supporters fight for you. Their username is legend.
<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, NowDevtools superstar Tushar Sadhwani (@tusharsadhwani) saves everyone's QA bacon with pytest-beartype, @beartype's newest official package. If you always wanted to test-drive @beartype but were too afraid to risk becoming homeless when the whole thing backfired on your last working production server, 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:
Install this steaming hot thing:
pip install --upgrade beartype
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:
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:
# 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: 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!
<sup>pytest-beartype surveys all it has done. conclusion: "this is fine"</sup>
@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.
<sup>@beartype 0.17.0: "Extruded alien protein chunks in my dinner?"</sup>
@beartype 0.17.0 also hallmarks the beginning of Beartype: Phase II. The central theme here is deep type-checking (i.e., type-checking both that objects are of the expected types and recursively type-checking some or all items contained in those objects). Now that @beartype shallowly type-checks almost everything, it's time to dive into the deep end. Over the course of 2024, @beartype will gradually roll out:
O(1) constant time.O(n) constant time. When we do this, we'll couple this to an actual deadline scheduler preventing @beartype from consuming more than some preconfigured ratio of wall-clock time.You may now be thinking:
"But does @beartype 0.17.0 actually do anything?"
...heh. Let's begin.
<sup>@beartype 0.17.0: "Mutated alien alligators ain't no thang."</sup>
@beartype 0.17.0 massively increases the configurability of @beartype. Because everybody always wanted to:
violation_*type, then win.violation_*type grepping intensifies.__instancecheck_str() enters the chat emboldened and swaggering.violation_verbosity + BeartypeVerbosity is snickering in the back.type {name} = {hard_stuff} | {moar_stuff}.hint_overrides + BeartypeHintOverrides. It's best not to question this stuff.These improvements were made possible only by the code-bending thaumaturgy of Montreal API snow wizard @felixchenier (Félix Chénier), who exhaustedly pushed numerous pull requests (PRs) across the git finish line. @beartype is now something actually usable by living humans that breathe oxygen. As a token of our gratitude, please accept this animated Ultraman GIF.
<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...
Like everyone, I used to hate the universal beartype.claw.beartype_all() import hook up until five minutes ago. By default, beartype.claw.beartype_all() dangerously raises fatal exceptions on type-checking violations that occur anywhere in your full app stack – including in code you do not own, have no control over, and mostly could care less about. But what if you could 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:
# 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:
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.
<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, we feel happy about this startling explosion of unmaintainable technical debt and bewildering code complexity.
<sup>is that what our lives have come to</sup>
violation_*type: When You Know Better, You Better Tell @beartypeBy default, @beartype raises thoughtful but eyebrow-raising type-checking violations with exception types like beartype.roar.BeartypeCallHintParamViolation 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:
violation_type, the default type of exception raised by @beartype when a type-checking violation occurs – any type-checking violation, including:
die_if_unbearable() violates a type-check.@beartype-decorated callable violates a type-check.@beartype-decorated callable violates a type-check.Most users who want to configure violations want to pass this option. Defaults to None, in which case @beartype preserves backward compatibility by just doing what it currently does – which is perfectly fine, of course. No shade on @beartype defaults. Obsessive-compulsives may also fine-tune:
violation_door_type, the type of exception raised by @beartype when an object passed 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:
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
))
<sup>does that dude in the back really have drills for arms? really? so cool</sup>
violation_*type: When Exceptions Are Too Scary for the Slumber PartyWarnings are exceptions in Python. I know, right? Who knew. All these years. The builtin Warning class subclasses the 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().
Is there a real-world application? There are so many I cannot count them all on my vestigial drill hands. Pretending that @beartype is mypy is one. This is another: gradual adoption.
Imagine a monolithic codebase named FuglyBugs that hates you. That codebase is a sprawling million-line ghetto of badly typed spaghetti whose most inventive feature is single-letter attribute names in the Unicode Tertiary Ideographic Plane. You're the new guy next to the gurgling water cooler that leaks suspicious fluid all over the floor according to a Poisson distribution with a high λ. It's do or die. The garbage is piling up in the corridor. Your grizzled landlady is hissing about "overdue rent" or something. Who cares, landlady? But the cats are hissing as well. You can't throw @beartype directly at that codebase without destroying your nascent life story. So what a somber devops goin' do?
You gradually adopt a QA bear cub today, the @beartype way:
# 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.
<sup>FuglyBugs! spit it! who did this 2 u!?</sup>
violation_verbosity + BeartypeVerbosity: Massage Your Brain with BeartypeLet us breathe out and then back in and then... actually please keep doing that. Let it never be said that @beartype gives bad advice. Wait. What is this, a yoga class in our release notes? What were we talking about again? Which is an appropriate lead-in to...
Verbosity. Prior versions of @beartype were verbose (like this changelog). Type-checking violation messages included the full contents of the current beartype configuration, which now contains an infinite "wealth" of options.
@beartype 0.17.0 curtails that insanity by dialing down on the prolix nebulosity. Beartype configurations are no longer embedded in violations by default, because your sanity is our personal responsibility. Somebody hates this change and is now thinking: "But I like the old way. I like parking tickets, too."
@beartype 0.17.0 is here for that somebody, introducing a new violation_verbosity option that governs violation verbosity. The value of this option is one of the following members of our 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 handBeartypeViolationVerbosity.MAXIMUM, extending the default verbosity with additional metadata intended for inadvisable all-nighter debugging sessions that end in shaking, weeping, and puffy cheeks. This includes:
violation_verbosity defaults to BeartypeViolationVerbosity.DEFAULT, because your brain is a precious quantity. But you know better.
# 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*
<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 – 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. 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:
# 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:
# 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".
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>
<sup>caring means punching a monster in the gut for humanity</sup>
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:
# 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.
<sup>so this is what feels like when monsters cry</sup>
@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!
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.
<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:
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:
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:
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:
__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.
<sup>just another day at the kaiju office ends on a terrifying footnote</sup>
@beartype 0.17.0 now sorta supports PEP 695-compliant type aliases (i.e., type hints with simple names whose values are other more complex type hints, instantiated by statements of the form type {alias_name} = {alias_value} under Python ≥ 3.12). Type aliases are useful for improving the readability of type-checking violations. Type aliases are also mostly broken by runtime CPython deficiencies. One out of two ain't bad.
The numpy.typing.ArrayLike union is the canonical use case. Shield your child's eyes from this abomination beyond from the Chaos Gate:
# 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:
>>> 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:
# 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!
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.
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.
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.
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...
# 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:
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:
type aliases across your entire codebase to a unique submodule conditionally imported only under Python ≥ 3.12.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:
# 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.
<sup>that feeling when you realize you wasted ten minutes of your life</sup>
A brief history in futility and the sound of one keyboard clapping.
A decade (but what feels like a lifetime) ago, CPython devs made the ignominious decision to externalize all type hints for the standard library into a third-party package inaccessible to runtime type-checkers named typeshed. According to the Python mailing list, "Accurate typing is hard!" I am now heaving my emaciated arms up into the air. You already did the hard work in 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>
typeshed devs intend to annotate something in the standard library for which no standard type hints exist.__getitem__() dunder method) without documenting anything or standardizing the semantic meaning of the resulting object.mypy devs quietly interpret subscription of that type as a non-standard type hint in an obscure and undocumented mypy-specific manner.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:
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:
weakref.weakref[T] type hints to the weakref.weakref class.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.
@beartype 0.17.0: all the bad stuff, now in one easy convenient package.
<sup>only a flying water bottle on fire holds the key to planetary salvation</sup>
ohmygodswontthischangelogquitalready
Apparently, this is fine now:
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.
<sup>Cobra Guy encourages at-risk codebases to try a bit harder</sup>
Lastly, @beartype 0.17.0 officially:
Supports @patrick-kidger's Equinox now. JAX + ML + @beartype = pretty sure you just got a raise.
Does not support nptyping, because (A) nptyping is dead and (B) nptyping is bad. Dead and bad are both bad. If you attempt to 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.,
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 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.
The data science pipeline you save might just be your own. It's probably too late, though.
<sup>your codebase when you realize you used nptyping everywhere</sup>
Previously, I publicly begged for GitHub Sponsors with a flashy logo believed to be reminiscent of sketchy underground raves – which, of course, none of us know anything about or have ever attended. <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, 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.
<sup>@beartype userbase: make a better world through glowing orbs</sup>
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.
<sup>@beartype feels that feeling, too</sup>
This patch release resolves all the bad things that have quietly gone unnoticed by both man and Maine Coon alike... until now. Fellow Ontarian and ML
This patch release resolves all the bad things that have quietly gone unnoticed by both man and Maine Coon alike... until now. Fellow Ontarian and ML superstar @MaximilienLC (Maximilien Le Cleï) <sup>seriously, what is up with that "ï"</sup> quietly called our attention to a bevy (pretty sure that means "alot") of outstanding badness riddling the @beartype codebase.
This patch release resolves that badness. This means:
beartype.claw + methods + PEP 526. Previously, beartype.claw silently failed to type-check PEP 526-compliant annotated variable assignments in methods. Now, beartype.claw does so: e.g.,
# In "{your_sagacious_package}.__init__":
from beartype import beartype_this_package
beartype_this_package()
# In "{your_sagacious_package}...{your_bodacious_module}":
class SoMuchClass(object):
def so_much_method(self) -> None:
# "beartype.claw" now raises an exception on this violation. Yah!
so_much_local: int = 'You no longer fool @beartype, local."
# "beartype.claw" also raises an exception on this violation. Go!
self.so_much_var: int = 'You too are known to @beartype, variable."
@beartype 0.16.3 inheritance regression. @beartype's prior stable release (i.e., @beartype 0.16.3) introduced a critical regression with respect to inheritance and type-checking. Notably, the @beartype decorator silently failed to type-check subclass methods overriding superclass methods under @beartype 0.16.3. Now, it does. I implore you all to believe that this never happened... Believe!
Much thanks to @MaximilienLC for his all-seeing eye, which sees all @beartype's typing failures as plainly as I see the blinding glare off my bald and malding head. (Pretty itsy-bitsy nitty-gritty, innit?)
This bug-defying patch release adds official support for hot module reloading, root superclass validators, forward reference `issubclass()` proxying,
This bug-defying patch release adds official support for hot module reloading, root superclass validators, forward reference issubclass() proxying, readable forward reference exceptions, and class redecoration eliding as well as documenting a medley of topics and APIs first introduced with the beartype.claw subpackage under @beartype 0.15.0. Where did the time go? Probably playing vidya games, if I'm being openly honest with myself.
@beartype 0.16.3 almost qualified as a full-blown minor release called @beartype 0.17.0. In the end, however... you failed, @beartype 0.16.3! You weren't quite big enough, dizzyingly stupefying enough, or blatantly broken enough to get upgraded to a minor release. It's for the best.
Look. It was Canadian Thanksgiving. It was all I could do to take the roasted turkey leg out of my mouth. Still, there is awesome sauce. This includes:
Hot reloading. @beartype is now robust against hot reloading (i.e., re-importation of previously imported modules containing one or more @beartype-decorated classes), resolving issue #288 kindly submitted by awfully ingenious Cambridge researcher @awf (Andrew Fitzgibbon). The @beartype decorator now explicitly (in order):
@beartype-decorated class with the same name in the same module, usually but not necessarily due to hot reloading).Root superclass validators. The @beartype decorator now supports beartype validators of the form typing(|_extensions).Annotated[object, beartype.vale.Is*, ...] (i.e., PEP 593-compliant type hints annotating the otherwise ignorable root object superclass by one or more unignorable beartype validators), resolving both issues #290 kindly submitted by Plum maestro @wesselb (Wessel) and beartype/plum#120 kindly submitted by professional hodge-podger @hodgespodge. With the fearsome power of root superclass validators, validate that arbitrary objects satisfy various constraints regardless of the actual types of those objects. This is now a thing:
>>> from beartype.door import is_bearable
>>> from beartype.typing import Annotated
>>> from beartype.vale import Is
>>> ICanHazAttr = Annotated[object, Is[
... lambda value: hasattr(value, 'i_can_haz_attr')]]
>>> is_bearable('hello', ICanHazAttr)
False # <-- y u no got that attr, "str" class!?
>>> class IHazAttr(object):
... i_can_haz_attr = 'Totally got this one, bro.'
>>> is_bearable(IHazAttr, ICanHazAttr)
True # <-- kk, you gots that attr
Forward reference issubclass() proxying. The @beartype decorator now supports subscripted forward references (e.g., "type[MuhClass]") to proxy both isinstance() and issubclass() type-checks, resolving issue #289 kindly submitted by Google X extraordinaire @patrick-kidger (Patrick Kidger). Previously, subscripted forward references erroneously proxied only isinstance() type-checks; this omission prevented these references from correctly resolving stringified type hints of the form type[{UndefinedClass}] (i.e., subscriptions of the PEP 585-compliant type[...] builtin by a forward reference to a class that has yet to be defined). Now, all is full of QA. Praise be to the Kidger: e.g.,
from beartype import beartype # .-- this really hot ASCII art
# | arrow means this works now
@beartype # v
def dance_beartype_dance(i_dont_wanna: 'type[YoullDanceAndLikeIt]'):
pass
class YoullDanceAndLikeIt(...): ...
Readable forward reference exceptions. The @beartype decorator now raises human-readable exceptions involving forward references. Previously, forward reference proxies insanely presented themselves as unreadable private @beartype classes like beartype._check.forward._fwdref._BeartypeForwardRefIndexable. Now, forward reference proxies quietly pretend they're just the classes they proxy. They're not, but you're no longer supposed to know. Sure... okay. Look. This is a bald-faced lie, but @beartype is okay with lying to your face git blame when doing so is in your best interests. Specifically, forward reference proxies now additionally proxy both:
Class redecoration eliding. The @beartype decorator now efficiently protects itself against redecoration. Previously, @beartype uselessly allowed classes already decorated by @beartype to be redecorated by @beartype. Now, @beartype usefully ignores attempts to redecorate classes: e.g.,
@beartype # <-- this now reduces to a noop
@beartype # <-- this still does nice stuff
class MuhRedecoratedClass(...): ...
Documented stuff. Read such risible, readily defensible, and easily digestible documentation as:
beartype.claw.beartype_this_package() import hook. Ideally, this is what almost everyone should now be using.beartype.claw API page. Every beartype import hook has now been exhaustively documented. Okay, okay. We omitted the beartype.claw.beartyping() context manager, because we ran out of time and Cyberpunk 2077 isn't going to hack its own neural link, is it? It might, actually. We were warned!This is @beartype 0.16.3, the patch release best described as...
When you strive for Mount Olympus, yet you're still in Hades.
— @leycec, excerpts from "My Life with Beartype: A Sad Story"
This is how the QA was won. Not with a whimper, but an exploding head emoji. :expressionless: → :exploding_head:
This thrilling, chilling, and drink-spilling patch release resolves significant incompatibilities with respect to:
This thrilling, chilling, and drink-spilling patch release resolves significant incompatibilities with respect to:
@classmethod-decorated methods) + PEP 563 (i.e., from __future__ import annotations). If you use Plum – which you surely do, of course – you want this. Long live @wesselb, @PhilipVinc (Filippo Vicentini), and the fearless Plum crew who have quietly hypnotized you into forgetting about Julia. "What Julia?", you are now wondering. Am I right? You know I'm right."1.26.0") while failing for version strings describing unstable releases (e.g., "1.26.0rc1"). Now, that heuristic has been dramatically generalized and exhaustively unit-tested to support both. Long live @mgorny (Michał Górny) and the tiger-like Gentoo Linux crew, who have shown you the way to a better [read: terrifyingly computationally intensive yet obsessive-compulsively configurable] Linux distro.(Vent, ents, about an entrancing dance movement!)
Nothing published for this version
@beartype 0.16.0 [codename: _Super Unsexy Stabilization Asinine Force (SUSAF)_] boringly stabilizes everything unstable about @beartype that made your
@beartype 0.16.0 [codename: Super Unsexy Stabilization Asinine Force (SUSAF)] boringly stabilizes everything unstable about @beartype that made your coworker who only wears skinny jeans while squinting whenever you mention @beartype stop doing those things. Fix everything bad, SUSAF!
python3 -m pip install --upgrade beartype
@beartype 0.16.0 mostly introduces nothing new. Instead, @beartype 0.16.0 just fixes everything that's been broken for several years. We all tried to pretend that those things worked. I'm grateful for that. You were willing to look the other way while @beartype insanely stumbled around with its paws up in the air. Now, @beartype finally makes good on one or two of its historical promises.
To introduce our SUSAF feature list, @beartype presents... super unsexy anime ugly man Rock Lee!
<sup>Seriously. Those eyes. Those eyebrows. That bowl cut. Seriously.</sup>
All of these things and more now fully work as intended:
"List[MuhClass[str]]". Party! :partying_face:from __future__ import annotations. Never actually do this. :man_dancing:@classmethod + @property. Never actually do this, either. :woman_dancing:typing.NewType() objects. Compose those new types together, because you can! :expressionless:beartype.claw warnings. beartype.claw now tries to be quieter, but fails! :face_exhaling:beartype.claw + python -m {your_package}.{your_module}. It just works! Maybe! :smiley:@beartype + enum.StrEnum. You know somebody wanted this! :whale:@beartype now supports basically everything. @beartype should no longer raise decoration-time exceptions about unsupported type hints, because all type hints should now be supported. Because we just used weasel words like "basically" and "should," you "basically" "should" not trust anything I just wrote. Instead, please throw beartype.claw.beartype_this_package() against the bug-stained windshield of your favourite codebase. Let us know if @beartype is still broke af for your use case.
Previously, everyone blacklisted @beartype from decorating one or more problematic classes, methods, or functions with the standard @no_type_check decorator. Now, you no longer need to that. Unleash the bear. Let @beartype claw its disturbingly hungry way through your disconcerting infestation of bugs bug-free codebase.
Taiko drum roll, please.
<sup>Above: @leycec in a troubled moment during the 0.16.0 release cycle.</sup>
But first... a huge thank you to @beartype's growing retinue of one-time and recurring sponsors! I'm incredibly touched in a good way by everyone's outpouring of hard-earned biosecurity tickets. You know who you are... but nobody else did. Until now.
@beartype sponsors, please line up to receive your complimentary sugar-free Bear Claw of Undulating Adulation & Unending Congratulations:
I also kinda feed bad. I do intend to deliver a high-quality QA product of blood, sweat, tears, memes, and fun – but I also frequently drop the ball and fall short of that lofty goal. Moreover, money is increasingly hard for many to find in 2023. The post-scarcity future that Ian Banks promised that I personally would live to see in depressing sci-fi books about Culture – the mythological utopian society that I will probably never live to see – has never seemed further away.
Thank you. So much.
<sup>The true form of @beartype is revealed – and also thanks you.</sup>
Up next... @beartype 0.16.0 did actually do something novel. Surprise! It's:
${BEARTYPE_IS_COLOR}: Make the Bad Color Go Away@beartype 0.16.0 introduces our first official shell environment variable: ${BEARTYPE_IS_COLOR}. Through the power of ${BEARTYPE_IS_COLOR}, you too can now enforce a global colour policy by externally configuring our popular BeartypeConf.is_color option from the command line. As with is_color, ${BEARTYPE_IS_COLOR} is a tri-state boolean with three possible string values:
BEARTYPE_IS_COLOR='True', forcefully instantiating all beartype configurations across all Python processes with the is_color=True parameter.BEARTYPE_IS_COLOR='False', forcefully instantiating all beartype configurations across all Python processes with the is_color=False parameter.BEARTYPE_IS_COLOR='None', forcefully instantiating all beartype configurations across all Python processes with the is_color=None parameter.Force beartype to obey your unthinking hatred of the colour spectrum. You can’t be wrong!
BEARTYPE_IS_COLOR=False python3 -m monochrome_retro_app.its_srsly_cool
<sup>When @beartype makes your eyes bleed, call ${BEARTYPE_IS_COLOR}.<sup>
Previously, @beartype only supported simple forward references consisting of one or more "."-delimited Python identifiers like "MuhClass" and muh_package.muh_module.MuhClass. Now, @beartype finally supports complex forward references consisting of arbitrary combinations of references to classes that have yet to be defined both subscripted by and subscripting type hint factories.
In other words, forward references just work now. Bask in the ruthless complexity of @beartype now fully resolving a subscripted generic type alias forward reference (i.e., a stringified type hint referring to a global attribute that has yet to be defined whose value is a subscripted generic that also has yet to be defined). It hurts – but @beartype is here to help:
from beartype import beartype
from beartype.typing import Generic, List, TypeVar
@beartype
def this_is_fine(still_fine: 'WonkyTypeAlias') -> 'List[WonkyTypeAlias]':
return [still_fine]
# Type variable. Once upon a time you dressed so fine, @beartype.
T = TypeVar('T')
# Generic. Threw the bums a dime in your prime, didn't you, @beartype?
class WonkyGeneric(Generic[T]): pass
# Type alias. People call say, "Beware, doll. You're bound to fall, @beartype."
WonkyTypeAlias = WonkyGeneric[int]
# Prints:
# [<__main__.WonkyGeneric object at 0x7f27e21b1f50>]
print(this_is_fine(WonkyGeneric())
Like a rolling stone, @beartype now brings it all back home.
Sadly, none of this is free. There are now space and time costs associated with forward references that you should be aware of. For most codebases, these costs will be negligible and thus ignorable. For some codebases, however, <sup>...especially codebases enabling PEP 563 via from __future__ import annotations</sup>, these costs could gradually become non-negligible and thus unignorable. Let's unpack what exactly is happening above.
this_is_fine() function is annotated by two stringified type hints (i.e., type hints that are strings describing standard type hints rather than standard type hints).List attribute has been previously imported, @beartype replaces List in the return annotation with the previously imported beartype.typing.List attribute. Now:
this_is_fine() resembles def this_is_fine(still_fine: 'WonkyTypeAlias') -> List['WonkyTypeAlias']:. The only remaining stringified attributes refer to 'WonkyTypeAlias', which has yet to be defined._BeartypeForwardRefIndexable. (It's best not to think too hard about this. @leycec sure didn't.) Now:
this_is_fine() resembles def this_is_fine(still_fine: _BeartypeForwardRefIndexable('WonkyTypeAlias')) -> List[_BeartypeForwardRefIndexable('WonkyTypeAlias')]:. No stringified attributes remain. All previously stringified type hints have now been replaced by standard type hints. Well, sort of standard type hints. I mean, we're all just squinting at this point.this_is_fine() function with a dynamically generated wrapper function performing runtime type-checking. B-b-but... how does @beartype even do that!?!? After all, neither the WoknyGeneric nor WonkyTypeAlias global have been defined yet. Ah-ha! Here is where the magic and the madness happens. Remember those forward reference proxies like _BeartypeForwardRefIndexable('WonkyTypeAlias') that @beartype previously injected into your type hints? Right. Those now do something. Each forward reference proxy now pretends that it is a normal class. From this point on, as far as @beartype and everything else in the Python ecosystem is concerned, forward reference proxies are just normal classes. You type-check them like you would any normal class (i.e., by passing them as the second argument to the isinstance() builtin). @beartype's code generation algorithm doesn't know any better. Ignorance is bliss when you are tired, it's Friday night, and you just want to play five hours more of Baldur's Gate 3 already.WonkyGeneric and WonkyTypeAlias global attributes.this_is_fine() function. Magic and madness erupt. At last! Remember those forward reference proxies? They're baaaaaack. They finally do something. The type-checking code that @beartype previously wrapped this_is_fine() with kicks in by:
isinstance() builtin, which then...__instancecheck__() dunder method defined by the private metaclass _BeartypeForwardRefMeta of that forward reference proxy, which then...is_instance() method defined by that forward reference proxy, which then...WonkyTypeAlias from your module, which has now been defined.WonkyGeneric[int] rather than an actual type.WonkyGeneric, which is an actual type.isinstance() builtin."My head just exploded and I hurt all over. Please. Stop talking." – you, probably
This is an understandable response when @leycec starts talking. The laborious point I am trying and failing to make here is that forward references now incur costs. Ignoring the ridiculous explosion in code complexity, there are now unavoidable space and time costs here. Types are being cached all over. eval() is being called all over. Dynamic forward reference proxy classes are being instantiated all over.
@beartype optimizes this as much as feasible, because that's why we're here. Realistically, though? There's just soooo much magical dynamism here. @beartype can only safely optimize so much. You don't even want to know what @beartype does for fully-qualified forward references like 'muh_package.muh_module.MuhGeneric[T]'. Let us just say that it is Trash Day, your trash bins are now overflowing with stinky refuse, and Python's garbage collector is parked out front eviscerating the steaming mountain of short-lived objects in generation 0 that you have inflicted upon it.
Sometimes, you just need to forward reference. That's fine. @beartype is now here for you. Just try to keep it low-key. KK?
Oh – and here's what @leycec's idealistic and better-adjusted younger self had to say about this:
omg! OMG! OMGGGGG!!! @TeamSpen210: Your brilliant five-sentence abstract on the nature of Python vis-a-vis forward references actually worked! It actually worked, bro! Punch the air like you just don't care, 'cause that's what we're doin'.
Naturally, your "simpler approach" was horrifying. My once-beautific bald face is crinkled with wrinkles, my twitchy eye is even more twitchy, and our cat won't stop pacing the room. I spent the entirety of my vacation doing this – and I don't even need this in production code. It just became a titanic struggle of @leycec versus @beartype versus Python: a three-way untelevised cage match that could only end with one man hurtling fifty feet onto another man lying prostrate on a table perched on top of another man curled in the fetal position while bleeding out live on GitHub.
<sup>...dat the best u can do, python?</sup>
<sup>wat is even happening here</sup>
<sup>When your masterplan only ends in tragedy for everybody.</sup>
Seriously. I've got five hundred line comments devsplaining single one-liners. I've got 100KB of incomprehensible pure-Python isolated to four new submodules sprawled across a new subpackage devoted exclusively to inchoate madness. I've got abstract base classes, abstract metaclasses, dynamic type factories, dunder methods on sunder methods on abstract cached properties. I've got friggin' metaclass
__getattr__()methods performing deferred module attribute importation via dynamic class attribute lookups of attributes that don't actually physically exist anywhere. I've got two wholedict.__missing__()implementations that don't even really have anything to do with one another. This is inchoate madness.
I don't even know how any of this works anymore. I've already jettisoned all knowledge pertaining to this topic so I can just sleep tonight without fitfully crying out in the night for my childhood stuffed octopus. I wish to awaken from the real-world nightmare I have unwittingly exposed myself to. It turns out Bloodborne is more a state of mind rather than a mere video game.
<sup>Python. It do be like that.</sup>
Forward references: do not go there, lest this animated reenactment of @leycec's life corrupt your once-pure essence.
I'm pretty sure (but not certain) that I may have inadvertently summoned the Eldritch She-God Shub-Niggurath, The Black Goat of the Woods with a Thousand Young, during the resolution of this feature request. I apologize, humanity. I'm so sorry! I didn't mean to open that portal. It just opened of its own accord while the guard cat was indolently passed out last night.
It happened. @leycec remembers.
<sup>Kumamoto Bear remembers, too.</sup>
And then there was PEP 563 via from __future__ import annotations – the dread PEP whose name shall not be spake that we just spake. We fundamentally reimplemented our entire PEP 563 pipeline in terms of our forward resolution mechanism from Hell. (See: above.)
From @beartype's perspective, PEP 563 basically no longer exists. @beartype no longer particularly cares whether you explicitly stringify your type hint likes 'List[WonkyTypeAlias]' or implicitly stringify your type hints via from __future__ import annotations. The end result is the exact same: stringified type hints.
Do we all now understand why PEP 563 is bad? We do. But let us tiresomely enumerate the ways, anyway:
Here. We're gonna give it to you straight. If you enable PEP 563 across only a small number of modules, it's unlikely that you will see a measurable performance hit. If you enable PEP 563 across your entire friggin' codebase, however? Yeah. Expect those performance hits. Your apps will startup slower, run slower even after they spin up, and possibly even catastrophically fail in pernicious edge cases.
Don't be that guy. Don't enable PEP 563. Not even once.
<sup>Would you trust a man whose neck is also his chin? @beartype would.</sup>
@wesselb's magnum opus to @beartype-based multiple dispatch now works better. Notably:
from __future__ import annotations. I mean, you shouldn't. But you can. Maybe. But... yeah. I still wouldn't.typing.Literal[...] type hints now works considerably better. @PhilipVinc: you know who you are.<sup>Plum: even the poor little doggo agrees</sup>
Greets to @empyrealapp, @PhilipVinc, @rlkelly, @KyleKing, @RomainBrault, @tolomea, @skeggse, @alexander-c-b, @gabrieldemarmiesse, @machow. You know who you are. You are... awesome!
<sup>i luv u</sup>
It was SUSAF. It was... @beartype 0.16.0.
@beartype 0.15.0 arises from the ashes of our issue tracker. Now, so too will your codebase.
@beartype 0.15.0 arises from the ashes of our issue tracker. Now, so too will your codebase.
python3 -m pip install --upgrade beartype
Like a cyberpunk phoenix whose intertube veins are made of pure honey and blueberry juice, @beartype 0.15.0 introduces the new beartype.claw API. Automate the @beartype decorator away with magical import hooks from beartype.claw. Do it for the new guy that's sobbing quietly in his cubicle.
When you call import hooks published by the beartype.claw API, you enable hybrid runtime-static type-checking. By "hybrid runtime-static," we mean that beartype.claw performs both standard runtime type-checking (ala the @beartype decorator) and standard static type-checking (ala mypy and pyright) but at runtime – and that ain't standard.
That's right. @beartype is now a tentacular cyberpunk horror like that mutant brain baby from Katsuhiro Otomo's dystopian 80's masterpiece "Akira." You can't look away!
<sup>May Neo-Tokyo have mercy on @beartype's soul.</sup>
Does this mean that you can now safely discard mypy, pyright, and every other pure-static type-checker that your enlightened IDE has unjustly subjected you to over the past several years? In general:
type: ignore[...] and pyright: ignore[...] comment chatter throughout your once-pristine codebase, and fail to enforce anything at test- or runtime. In other words, they (mostly) suck; we should all stop using them, because they (mostly) fail at their core mandate.typing.LiteralString type hint. That's critical.In either case, beartype.claw lies less,<sup>...in most cases, much much less</sup> requires no comment chatter, and enforces everything at test- and runtime. You do still want a real-time IDE linter to catch mundane mistakes like trivial syntactic errors and semantics concerns like obviously undeclared attributes, of course; allow me to now shill for @astral-sh's magnum opus ruff here. If it barks, it's either ruff or that neighbourhood mongrel harassing our Maine Coons again. Those are domesticated cats, fat doggo – not raccoons. Why won't you listen to a human's plea in the night? :face_exhaling:
For those who are about to code, we salute you with all you need to know:
# In your "{your_package}.__init__" submodule:
from beartype.claw import beartype_this_package # <-- boilerplate for victory
beartype_this_package() # <-- congrats. your team just won.
That's it. That's beartype.claw in ten seconds (dyslexia notwithstanding). As the simplest of several import hooks published by the beartype.claw API, the beartype_this_package() function:
{your_package} by the @beartype decorator. Rejoice, fellow mammals! You no longer need to explicitly decorate anything by @beartype ever again. Of course, you still can if you want to – but there's no compelling reason to do so and many compelling reasons not to do so. You have probably just thought of five, but there are even more.muh_int: int = 'Pretty sure this isn't an integer.') to runtime type-checking via the beartype.door.die_if_unbearable() function. More below.Okay, that's not it. The beartype.claw rabbit hole goes so deep that we couldn't even document anything for this release. Because exhaustion defeated common sense, these release notes are all the documentation.
<sup>This is what happens when we don't beartype_this_package().</sup>
beartype.claw actually provides five vaguely related import hooks. In ascending order of scope, these are...
beartyping: The Context Manager That Does StuffThe beartype.claw.beartyping context manager is our most self-limiting import hook. When you want to temporarily type-check only a few packages and modules isolated to a single block of code, put that beartyping on speed dial:
# Anywhere you like, but typically in your "{your_package}.__init__" submodule:
from beartype.claw import beartyping # <-- boilerplate intensifies
with beartyping(): # <-- context managers: they manage context
import {your_package}.{your_module} # <-- @beartype your own stuff with pride
import {their_package}.{their_module} # <-- @beartype somebody else's stuff with or without pride
As the above example suggests, all beartype.claw import hooks apply equally well to code you and your fearless team have authored, other people's third-party code, and Python's standard library. Whether they intended you to @beartype their stuff or not, do it anyway. Shove @beartype's snuffling snout into every hidden nook and cranny of the Python ecosystem. If it feels good, improves quality assurance, and impresses that weird management guy, could it be wrong?
<sup>The journey of a thousand bugs begins with a single telekinetic leap. beartype.claw.beartyping: this is that leap.</sup>
beartype_this_package: It Does What It Says, Unlike @leycecThe beartype.claw.beartype_this_package() import hook isolates its bug-hunting action to the current package. This is what everyone wants to try first. If beartype_this_package() fails, there is little hope for your package. Even though it's probably @beartype's fault, @beartype will still blame you for its mistakes.
Typically called as the first statement in your top-level {your_package}.__init__ submodule, beartype_this_package() extends the surprisingly sharp claws of @beartype to all callables, classes, and PEP 526-compliant annotated variable assignments defined across all submodules and subpackages of the current package – regardless of lexical or filesystem nesting depth. As the term "import hook" implies, beartype_this_package() only applies to subsequent imports performed after that function is called; previously imported submodules and subpackages remain unaffected.
<sup>beartype_this_package(): it do be like that.</sup>
beartype_package: No One Expects GitHub BearThe beartype.claw.beartype_package() import hook isolates its bug-hunting action to the single package or module with the passed absolute name. Examples or it didn't happen:
# In your "{your_package}.__init__" submodule, this logic is exactly equivalent
# to the beartype_this_package() example above:
from beartype.claw import beartype_package # <-- boilerplate continues boilerplating
beartype_package('your_package') # <-- they said explicit is better than implicit,
# but all i got was this t-shirt and a hicky.
Of course, that's fairly worthless. Just call beartype_this_package(), right? But what if you wanted to confine beartype.claw to a single subpackage or submodule of your package (rather than your entire package)? In that case, beartype_this_package() is over-bearing.<sup>badum ching</sup> Enter beartype_package(), the outer limits of QA where you control the horizontal and the vertical:
# Just because you can do something, means you should do something.
beartype_package('your_package.your_subpackage.your_submodule') # <-- fine-grained precision strike
beartype_package() shows it true worth, however, in type-checking other people's code. Because the beartype.claw API is a permissive Sarlacc pit, beartype_package() happily accepts the absolute name of any package or module – whether they wanted you to do that or not:
# Anywhere you like. Whenever you want to break something over your knee,
# never leave Vim without beartype_package():
beartype_package('somebody_elses_package') # <-- blow it up like you just don't care
<sup>Truer words were never spoken, wizened psychic baby lady.</sup>
beartype_packages: When A Single Bear Just Won't DoThe beartype.claw.beartype_packages() import hook isolates its bug-hunting action to the one or more packages or modules with the passed absolute names. Whereas beartype_package() accepts only a single string, beartype_packages() accepts an iterable of zero or more strings. One function to QA them all, and in the darkness of our implementation bind them:
# In your "{your_package}.__init__" submodule, @beartype multiple packages
# including your own package. You live life unencumbered by rules, because you're
# making this up as you go along. Freedom means breaking things with your elbows.
from beartype.claw import beartype_packages # <-- boilerplate comes to a boil
beartype_packages((
'your_package',
'some_package.published_by.the_rogue_ai.Johnny_Twobits', # <-- seems trustworthy
'numpy', # <-- ...heh. no one knows what will happen here!
'scipy', # <-- ...but we can guess, can't we? *sigh*
))
<sup>The end of the road is where beartype_packages() is just getting started.</sup>
beartype_all: You Will Be AssimilateThe beartype.claw.beartype_all() import hook doesn't isolate anything! It's the ultimate extroverted import hook, spasmodically unleashing a wave of bug-hunting action across the entire Python ecosystem. After calling beartype_all(), any package or module authored by anybody (including standard packages and modules in Python's standard library) will be subject to @beartype.
This is the extreme QA nuclear option. Because this is the extreme QA nuclear option, most packages should not do this. beartype_all() forces possibly unwanted @beartype-ing on all downstream consumers importing your package. The only packages that should do this are high-level applications run as hegemonic executables (rather than imported by other higher-level applications and packages).
@beartype cannot be held responsible for the sudden rupture of normalcy, the space-time continuum, or your previously stable job. For those who are about to explode their codebases, we duck inside a bomb shelter:
# In your "{your_package}.__init__" submodule, this logic nukes Python from orbit:
from beartype.claw import beartype_all # <-- @beartype seemed so innocent, once
beartype_all() # <-- where did it all go wrong?
<sup>The beartype_all() lifestyle is short but sweet, just like @leycec.</sup>
This is PEP 526:
happy_fun_times: str = 0xDEADDEAD # <-- not so happy-fun after all, is it
Previously, PEP 526-compliant annotated variable assignments were beyond the feeble reach of the Bear. Now, the Bear lovingly fondles those assignments with runtime type-checking fuelled by our beartype.door.die_if_unbearable() function. Specifically, for each assignment of the form var_name: type_hint = new_value at any lexical scope of any module imported under an import hook described above, beartype.claw appends that assignment by a statement of the form die_if_unbearable(var_name, type_hint) type-checking that assignment against that type hint at runtime.
beartype.claw thus silently expands the above single-line assignment to: e.g.,
from beartype.door import die_if_unbearable # <-- boilerplate still boilerplating
happy_fun_times: str = 0xDEADDEAD # <-- you thought you could hide, bug. but you were wrong
die_if_unbearable(happy_fun_times, str) # <-- raise an exception like a dancing necromancer
Since the above assignment violates that type hint, beartype.claw will now raise a beartype.roar.BeartypeDoorHintViolation exception at the point of that assignment. beartype.claw ignores all variable assignments that are not annotated by type hints: e.g.,
happy_fun_times = 0xBEEFBEEF # <-- probably not happy-fun, but beartype no longer cares
If you hate this, you'll just love our new BeartypeConf.claw_is_pep526 configuration option. Which leads us directly to...
<sup>Unhappy people forgot to annotate their variable assignments, I see.</sup>
BeartypeConf: Now With More Umph in its ConfWe didn't tell you this, because we save the best for last. But all of the import hooks described above accept an optional keyword-only conf: BeartypeConf = BeartypeConf() parameter (i.e., user-defined beartype configuration, defaulting to the default beartype configuration). Unsurprisingly, that configuration configures all actions performed by the beartype.claw API under that import hook:
# In your "{your_package}.__init__" submodule, enable @beartype's support for the
# PEP 484-compliant implicit numeric tower (i.e., expand "int" to "int | float" and
# "complex" to "int | float | complex"):
from beartype import BeartypeConf # <-- it all seems so familiar
from beartype.claw import beartype_package # <-- boil it up, boilerplate
beartype_package('your_package', conf=BeartypeConf(is_pep484_tower=True)) # <-- *UGH.*
Equally unsurprisingly, our existing beartype.BeartypeConf dataclass has been augmented with new beartype.claw-aware super powers. Fine-tune the behaviour of our import hooks for your exact needs, including:
BeartypeConf(claw_is_pep526: bool = True). By default, beartype.claw type-checks PEP 526-compliant annotated variable assignments like muh_int: int = 'Pretty sure this isn't an integer.'. Although this is usually what everyone wants, this may not be what someone suspicious dressed in black leather, a red "velvet" cape, and aviator goggles wants in an edge case too unfathomable to even contemplate. If you are such a person, consider disabling this option to reduce runtime safety and destroy your code like Neo-Tokyo vs. Mecha-Baby-Godzilla: <sup>...who will win!?!?</sup># In your "{your_package}.__init__" submodule, disable PEP 526 support out of spite:
from beartype import BeartypeConf # <-- boiling boilerplate...
from beartype.claw import beartype_packages # <-- ...boils plates, what?
beartype_packages(
('your.subpackage', 'your.submodule'), # <-- pretend this makes sense
conf=BeartypeConf(claw_is_pep526=False) # <-- *GAH!*
)
BeartypeConf(warning_cls_on_decorator_exception: Optional[Type[Warning]] = None). By default, beartype.claw emits non-fatal warnings rather than fatal exceptions raised by the @beartype decorator at decoration time. This is usually what everyone wants, because the @beartype decorator currently fails to support all possible edge cases and is thus likely to raise at least one exception while decorating your entire package. To improve the resilience of beartype.claw against those edge cases, @beartype emits one warning for each decoration exception and then simply continues to the next decoratable callable or class. This is occasionally unhelpful. What if you really do want beartype.claw to raise a fatal exception on the first such edge case in your codebase – perhaps because you either want to see the full exception traceback or to punish your coworkers who are violating typing standards by trying to use an imported module as a type hint?<sup>...this actually happened.</sup> In this case, consider passing None as the value of this parameter; doing so forces beartype.claw to act strictly, inflexibly, and angrily with maximal roaring and blood-flecked claws:# In your "{your_package}.__init__" submodule, raise exceptions because you hate worky:
from beartype import BeartypeConf # <-- boiling boilerplate...
from beartype.claw import beartype_this_package # <-- ...ain't even lukewarm
beartype_this_package(conf=BeartypeConf(warning_cls_on_decorator_exception=None)) # <-- *ohboy*
<sup>Crack commando Bear-Team: Assemble!</sup>
...to financially feed @leycec and his friendly @beartype through our new GitHub Sponsors profile. Come for the candid insider photos of a sordid and disreputable life in the Canadian interior; stay for the GitHub badge and warm feelings of general goodwill.
Cue hypnagogic rave music that encourages fiscal irresponsibility. :musical_note: :musical_keyboard: :notes:
Greets to:
beartype.claw. @gabrieldemarmiesse went above the call of the wild, relentlessly previewing live unstable commits to the beartype.claw API against both open-source and proprietary projects. Two paws in the air, 'cause you know Gabriel care! :feet:beartype.claw breakage against real-world use cases – including @qutebrowser itself. This is the stuff dream teams are made of. High fives for the glory of Python. :hand: :hand:And... I'm spent. Clearly, this mega-issue is also spent. Fifteen million meme images and dissertation-length monologuing have clogged the Intertubes beyond repair. With an appreciable sigh of relief as we move into the new age of beartype.claw, let's close this venerable thread. All newer release announcements will be posted to our peanut gallery.
Goodbye, Future Sound of Beartype. Thanks for all the salmon.
<sup>beartype.claw rises with the paw of quality. Will you high-five that paw?</sup>
[PEP 585][PEP 585]. This release "undeprecates" the beartype.typing.{Match,Pattern} type hints deprecated by [PEP 585][PEP 585], resolving issue #240…
This patch release delivers enthusiastic hijinx capable quality assurance with improved support for third-party typing_extensions backports, PEP 517 (i.e., pyproject.toml), PEP 544 (i.e., typing.Protocol), and PEP 585 (i.e., re.Match and re.Pattern). Codebases everywhere can now release a grateful sigh of relief as bugs buckle under the combined might of @beartype + typing_extensions. Flex the muscles you knew you always had.
This patch release resolves 3 issues. Wave those paws in the air like your project manager just don't care!
pyproject.toml file in a vain and probably misguided attempt to restore the buildability of our documentation on the third-party ReadTheDocs (RTD) documentation host. Doing so nudges @beartype mildly closer towards abandoning the antiquated (and frankly objectionable) setuptools build system to Hatch, officially endorsed by the Python Packaging Authority (PyPA) as sane and not setuptools, which are the only criteria @leycec is looking for in a Python build system. The bar could not be lower.typing_extensions.Protocol backports, resolving issue #241 kindly submitted by MIT machine learning guru @rsokl (Ryan Soklaski). This release also restores testing of the typing_extensions.Protocol superclass, which now passes under all typing_extensions versions. Let's not ask prying and uncomfortable questions about what exactly was resolved here, because then @leycec might break down and openly weep emoji tears live on GitHub.beartype.typing.{Match,Pattern} type hints deprecated by PEP 585, resolving issue #240 kindly submitted by AI King @KyleKing (Kyle King). Specifically, the beartype.typing subpackage now imports those type hints from the standard re rather than typing module under Python >= 3.9. This is why @leycec sighs in his sleep while clutching a Bengal plushy.PEP 673 FAQ entry. This release documents why PEP 673 (i.e., typing.Self) officially supported by @beartype ≥ 0.14.0 is the substantially superior choice to either PEP 484-compliant forward references or PEP 563-compliant postponed type hints for type-checking a class self-referentially. Specifically, this release adds a new "...the current class?" question to our existing FAQ. In theory, this should clear up the smelly mountain of confusion surrounding this topic both on and off our issue tracker.
Project URL generalization. This release generalizes project URLs in our Sphinx configuration from the Sphinx-specific doc/src/conf.py script to the Sphinx-agnostic beartype.meta submodule, enabling generic reuse of those URLs across numerous third-party frameworks rather than merely Sphinx. In short, nothing worthwhile was done.
(Lush brush, last blast, and crass lass combine rash lashes!)
…pkg_resources package -- which now emits DeprecationWarning warnings implicitly coerced by pytest into test failures. Begone, foul pkg_resources!
This minor release brings exhilarating support for PEP 673 (i.e., typing.Self) and PEP 675 (i.e., typing.LiteralString) as well as substantially improved compatibility with PyPy.
This minor release resolves 2 issues. But first, a brief word from our tenebrous sponsors. They are gentlemanly alchemists who dispense truth and money while despoiling bugs in your codebase. All thumbs up, please!
A chorus of overwhelming jubilation chimes through the stuffy confines of the Bear Den. :clap: :polar_bear: :clap:
And now... the time we've waited for. A ribald display of plaintext that manhandles all twelve major meridian lines concurrently.
typing.Self). @beartype now fully supports typing.Self type hints in @beartype-decorated classes. Specifically:
typing.Self type hint annotating something inside the body of a class (e.g., method parameter or return, class variable), @beartype now reduces that hint to that class.typing.Self type hint annotating something outside the body of a class (e.g., function parameter or return, global variable), @beartype now raises an exception gently advising you to rethink life choices.typing.LiteralString). As PEP 675 advises for runtime type-checkers, @beartype now reduces (i.e., aliases) all typing.LiteralString type hints to the standard str type. Sadly, deeply type-checking literal strings is intractable for runtime type-checkers and is the textbook example of validation that can be performed only by static type-checkers. Type-checking literal strings requires:
id() builtin appears to occasionally [read: non-deterministically] return object identifiers that are negative integers. Specifically, @beartype now guaranteeably generates valid parameter names passed to type-checking wrapper functions regardless of the sign of the id() of the values of those parameters. Doing so resolves issue #232 kindly submitted by @jvesely (Jan Vesely) who purportedly lives in or around an ancient pork by-product that has calcified into stone -- which is quite impressive, really. Stoneham: it's like Stonehenge, only American and yummy in your tummy. Thanks so much for the heads up, @jvesely."Function", "Coroutine factory method") rather than the meaningless prefix "@beartyped". Explicitly detected types of callables include:
beartype._util.mod.utilmodtest.is_module_version_at_least() tester to cease deferring to the fragile third-party pkg_resources package -- which now emits DeprecationWarning warnings implicitly coerced by pytest into test failures. Begone, foul pkg_resources!beartype.roar.BeartypeDecorHintPep673Exception exception subclass.(Plucky puppies in a saggy rucksack!)
This patch release brings titillating support for working tests. That's right; the prior minor release broke tests by failing to ship the mypy.ini con
This patch release brings titillating support for working tests. That's right; the prior minor release broke tests by failing to ship the mypy.ini configuration file in tarballed sdists, thereby breaking the test_pep561_mypy() integration test when run from tarballed sdists. This is why we facepalm.
This patch release resolves 1 issue and merges 1 pull request. But first, a quiet word from our wondrous sponsors. They are monocled QA wizards who serve justice while crushing bugs for humanity. High fives, please!
Thunderous applause echoes through the cavernous confines of the Bear Den. :clap: :polar_bear: :clap:
And now... the moment we've waited for. A heinous display of plaintext that assaults all five senses simultaneously.
test_pep561_mypy() integration test validating that @beartype passes all mypy-specific static runtime type-checks, thanks to a pull request from @mgorny the Gentoo Guy Who Knows All and Codes All.(Vapid vapor pours rapidly!)
A Python 3.7-specific failure in our continuous integration (CI) workflow caused by Sphinx attempting to call deprecated functionality of the third-pa…
This minor release delivers pulse-quickening support for pandera (pandas) type hints, PEP 484, PEP 585, PEP 591, PEP 647, PEP 3119, and pseudo-callables. This release resolves 12 issues and merges 2 pull requests. But first: a quiet word from our wondrous sponsors. They are monocled QA wizards who serve justice while crushing bugs for humanity. High fives, please!
Thunderous applause echoes through the cavernous confines of the Bear Den. :clap: :polar_bear: :clap:
And now... the moment we've waited for. A heinous display of plaintext that assaults all five senses simultaneously.
DataFrame objects, produced by subscripting factories published by the pandera.typing subpackage and validated only by user-defined callables decorated by the ad-hoc PEP-noncompliant @pandera.check_types runtime type-checking decorator), resolving feature request #227 kindly submitted by @ulfaslakprecis (Ulf Aslak) the Big Boss Typer. @beartype now:
@pandera.check_types decorator for deeply runtime type-checking arbitrary pandas objects.O(1) isinstance()-based type-check for each pandera type hint. Doing so substantially improves usability in common use cases, including:
@pandera.check_types decorator.beartype.door.is_bearable().beartype.door.die_if_unbearable().pandera.typing submodule. Let us pretend this never happened, @ulfaslakprecis.collections.abc module. Now, @beartype permits:
collections.abc.AsyncGenerator type.collections.abc.Generator type.typing.Final[...] type hints), partially resolving issue #223 kindly submitted by the acronym known only as @JWCS (Jude). @beartype now trivially reduces all typing.Final[{hint}] type hints to merely {hint} (e.g., typing.Final[int] to int). In other words, @beartype no longer raises exceptions when confronted with final type hints and instead at least tries to do the right thing. This still isn't quite what everyone wants @beartype to do here; ideally, @beartype should also raise exceptions on detecting attempts to redefine instance and class variables annotated as Final[...]. Doing so is definitely feasible and exactly what @beartype should eventually do – but also non-trivial, because whatever @beartype eventually does needs to preserve compatibility with all implementations of the @dataclass decorator across all versions of Python now and forever. Cue that head-throbbing migraine. It's comin'! Oh, I can feel it!typing.TypeGuard[...] type hints), resolving feature request #221 kindly submitted by Google X researcher extraordinaire @patrick-kidger. @beartype now trivially reduces all typing.TypeGuard[...] type hints to the builtin bool type.__instancecheck__() dunder methods unconditionally raising TypeError exceptions) and non-issubclassable classes (i.e., classes whose metaclasses define PEP 3119-compliant __subclasscheck__() dunder methods unconditionally raising TypeError exceptions) more narrowly for safety, resolving issue #220 kindly submitted by extraordinary Google X researcher @patrick-kidger (Patrick Kidger). Notably, @beartype now only accepts TypeError exceptions as connoting non-isinstanceability and non-issubclassability. Previously, @beartype broadly treated any class raising any exception whatsoever when passed as the second parameter to isinstance() and issubclass() as non-isinstanceable and non-issubclassable. Sadly, doing so erroneously raises false positives for isinstanceable and issubclassable metaclasses that have yet to be fully "initialized" at the early time the @beartype decorator performs this detection.@beartype now supports pseudo-callables (i.e., otherwise uncallable objects masquerading as callable by defining the __call__() dunder method), resolving feature request #211 kindly submitted by Google X typing guru @patrick-kidger (Patrick Kidger). When passed a pseudo-callable whose __call__() method is annotated by one or more type hints, @beartype runtime type-checks that method in the standard way.README.rst -> Read the Docs (RtD), resolving both issue #203 kindly submitted by @LittleBigGene (AKA the dynamo of the cell) and ancient issue #8 kindly submitted by @felix-hilden (AKA the Finnish computer vision art genius that really made all of this possible). Readable documentation slowly emerges from the primordial soup of @beartype's shameless past for which we cannot be blamed. @leycec was young and "spirited" back then. This release:
README.rst documentation into a website graciously hosted by Read the Docs (RtD) subdividing that prior documentation into well-structured pages, resolving issue #203 kindly submitted by @LittleBigGene (AKA the dynamo of the cell).beartype.peps submodule), these undocumented APIs are assumed to either be sufficiently unpopular or non-useful to warrant investing additional scarce resources here.beartype.typing API._templates/sidebar-nav-bs.html template hack shamelessly copy-pasted into literally every project requiring this theme. This includes @beartype, because why not spew boilerplate that nobody understands everywhere? Sadly, doing so requires pinning to a maximum obsolete version of this theme that will surely die soon. And this is why I facepalm. These issues include:
README.rst documentation to a placeholder stub that just directs everyone to RtD instead.linecache integration commentary. Specifically, a pull request by @faangbait (AKA the little-known third member of Daft Punk) improves internal commentary in our private beartype._util.func.utilfuncmake.make_func() factory function responsible for dynamically synthesizing new in-memory functions on-the-fly. Our suspicious usage of None as the second item of tuples added as values to the standard linecache.cache global dictionary has now been documented. Thanks so much for this stupendous contribution, @faangbait!test_pep561_mypy() integration test to intentionally ignore unhelpful non-fatal warnings improperly emitted by mypy (which encourage usage of typing_extensions, oddly enough).test_beartype_in_sphinx() h0tfix is h0t. This release generalizes our test-specific test_beartype_in_sphinx() integration test to support arbitrary versions of Sphinx, resolving issue #209 kindly submitted by @danigm the sun-loving Málaga resident who frolics in the sea that Canadians everywhere are openly jealous of. Specifically, this release fundamentally refactors this integration test to fork a new Python interpreter as a subprocess of the current pytest process running the sphinx-build command.pkg_resources package. This release simply avoids installing Sphinx entirely under Python 3.7; although admittedly crude, it's unclear how else @beartype could possibly resolve this. Since Python 3.7 has almost hit its official End-Of-Life (EOL) and thus increasingly poses a security concern, this is hardly the worst resolution ever. Really! Believe what we're saying.Break nothing! It's the @beartype way. This is why @leycec cries like a mewling cat with no milk. (Thrilling chills spill towards an untoward ontology!)
beartype.roar.BeartypeAbby*Exception. This release deprecates all lingering remnants of the prior beartype.abby subpackage – including:
Beartype 0.12.0 expands the infinitely vast (yet mostly empty) universe of @beartype into the hitherto uncharted realms of configuration, exception identification, Nuitka, `typing.NamedTuple`, and Python 3.11. Also, other things were done. We swear it!
This minor release resolves 16 issues and merges 2 pull requests. But first, a quiet word from our wondrous sponsors. They are monocled QA wizards who serve justice while crushing bugs for humanity. High fives, please!
## Beartype Sponsors
[ZeroGuard: The Modern Threat Hunting Platform](https://zeroguard.com). All the signals, All the time.
Thunderous applause echoes through the cavernous confines of the Bear Den. :clap: :polar_bear: :clap:
And now... the moment we've waited for. A heinous display of plaintext that assaults all five senses simultaneously.
## Compatibility Improved
Python 3.11. This is the first @beartype support to officially support the recently released Python 3.11. Notably, this release: * Synchronizes our public beartype.typing subpackage against upstream changes in the standard typing module introduced in Python 3.11. * Supports PEP-compliant type hints subscripted by the empty tuple (e.g., typing.Tuple[()]), whose low-level implementation fundamentally changed under Python 3.11. * Updates our GitHub Actions-based continuous integration (CI) workflow to exercise @beartype against Python 3.11.
Nuitka. This is the first @beartype release to officially support Nuitka (i.e., the increasingly popular Python compiler that stuns us all), resolving feature request #197 kindly submitted by @shenwpo (also known as the giant flaming metallic letter e). This includes a new test_nuitka() integration test showing that Nuitka successfully compiles a minimal-length example (MLE) runtime type-checked by @beartype.
`typing.NamedTuple`. This release adds support for deeply type-checking subclasses of the PEP 484-compliant typing.NamedTuple superclass. Specifically, this release improves the resiliency of our PEP 563 resolution mechanism (i.e., the public beartype.peps.resolve_pep563() function) against callables whose __module__ dunder attributes lie. This includes all typing.NamedTuple subclasses, which synthesize callables whose __module__ dunder attributes erroneously claim to reside in the non-existent "namedtuple_Foo" module. Doing so resolves issue #181 kindly submitted by probably ingenious "Probabilistic Machine Learning" author @murphyk (Kevin P. Murphy).
## Features Added
Beartype configuration API. This release publishes a new public API for externally configuring @beartype via the now-official beartype.BeartypeConf type and beartype.BeartypeStrategy enumeration. Specifically, this release adds: * `beartype.BeartypeConf.is_color`, a new tri-state boolean enabling end users to control how and whether beartype colours type-checking violations (i.e., beartype.roar.BeartypeCallHintViolation exceptions) with POSIX-compliant ANSI escape sequences for readability, resolving issue #178 kindly submitted by the foxy ZeroGuard and River Oakfield founder @foxx (Cal Leeming). Rejoice, typing acolytes, for you have now been freed from the prismatic shackles of the rainbow! * `beartype.BeartypeConf.is_pep484_tower`, a new standard boolean enabling end users to control whether @beartype supports the [implicit numeric tower standardized by PEP 484](https://peps.python.org/pep-0484/#the-numeric-tower) or not, resolving issue #174 kindly submitted by dashing French Canadian @felixchenier (Félix Chénier). * `beartype.BeartypeStrategy.O0`, a new no-time strategy (i.e., beartype configuration option generalizing the standard @typing.no_type_check decorator). Enabling this strategy instructs the @beartype decorator to recall and preserve previously applied no-time strategies; internally, @beartype detects and reduces configurations resembling conf=BeartypeConf(strategy=BeartypeStrategy.O0, ...) to the @typing.no_type_check decorator. Users may now blacklist specific callables from being type-checked by configuring this strategy as documented in our front-facing README.rst documentation... somewhere. It's in there somewhere, people.
Beartype exception API. This release publishes a new public API for externally identifying the cause of type-checking violations (i.e., instances of the beartype.roar.BeartypeCallHintViolation exception class) raised by @beartype. These exceptions now publicly expose the user-defined objects responsible for those violations via a new BeartypeCallHintViolation.culprits property, resolving feature request #180 kindly submitted by @Jasha10 the Supremely Patient and Understanding GitHubber. For safety, this property dynamically returns a non-empty tuple of the one or more responsible culprits defined as either: * For each culprit that supports weak references and is still alive (i.e., has yet to be garbage-collected), that culprit as is. * Else, the machine-readable string representation of that culprit truncated to a reasonable number of characters.
## Features Deprecated
`beartype.roar.BeartypeAbby*Exception`. This release deprecates all lingering remnants of the prior beartype.abby subpackage – including: * beartype.roar.BeartypeAbbyException, supplanted by beartype.roar.BeartypeDoorException. * beartype.roar.BeartypeAbbyHintViolation, supplanted by beartype.roar.BeartypeDoorHintViolation. * beartype.roar.BeartypeAbbyTesterException, supplanted by beartype.roar.BeartypeDoorException.
## Static Type-checking Improved
@beartype exports. This release terminally pacifies: * Mypy by publicizing all exported attributes from the top-level beartype package via a new beartype.__all__ dunder attribute. Thanks to the stylishly pink-haired @pinkwah (Zohar Malamant) for the rapid pull request (PR). * pyright by explicitly re-exporting all public attributes of the top-level beartype package, resolving issue #169 kindly submitted by MIT AI mastermind @rsokl (Ryan Soklaski).
Continuous integration (CI). This release integrates our GitHub Actions-based continuous integration (CI) workflow (i.e., .github/workflows/python_test.yml) with third-party GitHub Actions statically type-checking beartype against both mypy and pyright at CI time– including on every commit as well as pull request (PR). For both robustness and efficiency, this release prevents functional tests in our test suite that perform these same static type-checks from running under CI. Doing so resolves a furious spate of spurious CI complaints. So what we did there? We rhymed. Notably, this release: * Leverages @jakebailey's superb jakebailey/pyright-action action to exercise @beartype against pyright at CI time. * Manually installs and runs mypy in a low-level manner under CI without leveraging @jpetrucciani's otherwise stellar jpetrucciani/mypy-check action -- which @beartype hopes to revisit at a later date when the issue tracker settles there a bit. Thanks so much, @jpetrucciani! You dah real QA MVP.
## Issue Resolved
`beartype.door.TypeHint` comparisons. This release significantly improves the robustness of comparison operators overloaded by the object-oriented beartype.door.TypeHint API, resolving issue #198 kindly submitted by @wesselb the phenomenal Amsterdammer of [Plum](https://github.com/wesselb/plum) fame. This includes edge cases when: * Comparing unions against both other unions and non-unions (e.g., typing.Any, isinstanceable classes). * Comparing tuple type hints against typing.Any.
`beartype.BeartypeConf` caching. This release resolves a critical (yet ultimately trivial) caching issue with respect to beartype.BeartypeConf singletons, in which singletons initialized with different parameters could conceivably have been erroneously cached to the same object. Hash collisions! I see hash collisions everywhere!
Call stack iteration robustness. This release resolves an edge case in our private beartype._util.func.utilfuncframe.iter_frames() generator iterating over stack frames on the current call stack. Specifically, this generator now safely reduces to the empty generator (i.e., noop) when the caller requested that generator ignore more stack frames than exist on the call stack. Although raising an exception would also be feasible, doing so would only needlessly increase the fragility of this already fragile mission-critical generator.
## Documentation Resolved
Broken anchor links. This release repairs broken anchor links dotted throughout our monolithic README.rst to actually point to valid (sub)sections.
Sphinx configuration. This release reconfigures the lackluster coffin that is our Sphinx configuration, en-route to resolving issue #8 (!) kindly submitted a literal lifetime ago by visionary computer vision export and long-standing phenomenal Finn @felix-hilden (Felix Hildén). Specifically, this release: * Enables Furo, switching from the default Read The Docs (RTD) Sphinx theme to the third-party Furo theme. We selected this theme according to mostly objective (albeit ultimately subjective) heuristic criteria. In descending order of importance, we selected the theme with:
The most frequent git commit history.
The open issues and pull requests (PRs).
3. The most GitHub stars as a crude proxy for aggregate rating. Furo handily bested all other themes across all three criteria. Furo is very well-maintained, frequently closes out open issues and merges open PRs, and sports the highest quantity of GitHub stars by an overwhelming margin. o/
Enables the builtin `intersphinx` extension, enabling attributes defined by the standard library (e.g., the typing module, the types.GenericAlias type) to be cross-referenced as a fallback when not already defined by this project.
Reconfigures RTD through our top-level .readthedocs.yml configuration to: * Build under the most recent Long Term Service (LTS) release of Ubuntu. * Build under the most recently released minor version of CPython. * Configure Sphinx via our doc/source/conf.py script.
Restores the standard `sys.path` hack – which, for unknown reasons, @leycec disabled but thankfully left commented out. Doing so re-resolves issue #120, kindly submitted by @kloczek (Tomasz Kłoczko) five friggin' months ago. Thanks so much for the fast patch and rapid turn-around, @kloczek!
Improves pathname robustness by intelligently detecting documentation paths via the standard pathlib.Path API.
Enables `autoapi`. This release successfully transitions from Sphinx's builtin (but insane) autodoc and autosummary extensions to Read The Doc (RTD)'s non-builtin (but sane) autoapi extension.
Adds a local URI store (i.e., hidden reStructuredText (reST) document centralizing common URI links in reST format, automatically exposed to all other reST documents in this project via the rst_epilog setting in conf.py) at doc/src/_links.rst.
Removes obsolete cruft, which accrues with time like entropic motes in God's eye. That was a reference to a Golden Age of Scifi book, people! Don't ask why God only has one eye. It's better not to contemplate these matters.
Ruthlessly circumvents upstream issue sphinx-doc/sphinx#4961, causing Sphinx to emit literally hundreds of ignorable warnings resembling "WARNING: more than one target found for cross-reference 'TypeHint': beartype.door._doorcls.TypeHint, beartype.door.TypeHint" with a [trivial circumvention shamelessly pilfered from @RDFLib](https://github.com/RDFLib/rdflib/blob/3a418218d6bcdb46f78342e14c024063e2f53e71/docs/conf.py#L255).
## Documentation Added
[Beartype Object-oriented API](https://github.com/beartype/beartype/tree/76aebd63b5f32ac6bdab6420eb0c9bfa2ca09b29#id36). This release prefaces our "Beartype Object-oriented API" subsection with a human-readable discussion of the Decidedly Object-Oriented Runtime-checking (DOOR) – also known as "That API Which Breaks Hearts and Minds Alike."
**[Procedural Showcase](https://github.com/beartype/beartype/#procedural-showcase). This release adds a new Procedural Showcase subsection containing a new Detect API Breakage subsubsection exhibiting a real-world usage for our recently published beartype.door.is_subhint() tester: detecting API breakage across the type hints annotating arbitrary callables in exactly ten lines of code.
[Near-real-time FAQ entry](https://github.com/beartype/beartype/#beartype-realtime). This release adds a new FAQ entry entitled What does "near-real-time" even mean?, justifying our recent categorization of @beartype as a "near-real-time runtime type-checker." Let's pretend @leycec knows what he's talking about.
[JAX, Numpy, and PyTorch FAQ entries](https://github.com/beartype/beartype/#jax-arrays). This release expands our existing FAQ with entries on typing JAX and NumPy arrays and PyTorch tensors to highlight the stupefying potential unlocked by the third-party jaxtyping, nptyping, and TorchTyping packages, resolving issue #98 submitted a literal lifetime ago by Edinburgh NLP researcher @amitkparekh (Amit Parekh).
[VSCode FAQ entry](https://github.com/beartype/beartype/tree/76aebd63b5f32ac6bdab6420eb0c9bfa2ca09b29#id28). This release rewrites our entire FAQ entry on pyright + Pylance + VSCode to be significantly more charitable towards pyright, resolving issue #170 kindly submitted by MIT AI mastermind @rsokl (Ryan Soklaski).
[Type narrowing FAQ entry](https://github.com/beartype/beartype/tree/76aebd63b5f32ac6bdab6420eb0c9bfa2ca09b29#id30). This release adds a new FAQ entry on type narrowing, strongly inspired by (...wait for it) MIT AI mastermind @rsokl (Ryan Soklaski)'s equally masterful writing at issue #166.
(Powerful bowers full of flowers!)
`beartype.cave` deprecation removals. This release removes all deprecated third-party attributes from the beartype.cave submodule. The continued exist…
Beartype 0.11.0 released.
This minor release unleashes a major firestorm of support for class decoration, colourful exceptions, pyright + PyLance + VSCode, the Decidedly Object-Orientedly Recursive (DOOR) API, the Python Enhancement Proposals (PEPs) API, PEP 484, PEP 544, PEP 561, PEP 563, PEP 585, PEP 604, PEP 612, and PEP 647.
This minor release resolves a mammoth 29 issues and merges 12 pull requests. Noteworthy changes include:
@beartype decorator now decorates both higher-level classes and lower-level callables (i.e., functions, methods), resolving feature request #152 kindly submitted by @posita the positively sublime. All possible edge cases are supported, including:
@classmethod.@staticmethod.@property.typing.Generic superclass and other typing pseudo-superclasses, resolving issue #140 kindly submitted by @langfield (William Blake – yes, that William Blake). Notably, this release extricated our transitive visitation of the tree of all pseudo-superclasses of any PEP 484- and 585-compliant generic type hint (...don't ask) from its prior hidden sacred cave deep within the private beartype._decor._code._pep._pephint submodule into a new reusable iter_hint_pep484585_generic_bases_unerased_tree() generator, which is now believed to be the most fully-compliant algorithm for traversing generic inheritance trees at runtime. This cleanly resolved all lingering issues surrounding generics, dramatically reduced the likelihood of more issues surrounding generics, and streamlined the resolution of any more issues surrounding generics should they arise... which they won't. Generics: we have resoundingly beaten you. Stay down, please.typing.Protocol + abc.ABC superclasses, resolving #117 kindly submitted by too-entertaining pun master @twoertwein (Torsten Wörtwein). Notably, @beartype now:
beartype.typing.Protocol superclass and parametrizations of that superclass by one or more type variables (e.g., beartype.typing.Protocol[typing.TypeVar('T')]) as semantically meaningless in accordance with similar treatment of the typing.Protocol superclass.beartype.typing.Protocol superclass to themselves be subclassed by one or more concrete subclasses. Previously, attempting to do so would raise non-human-readable exceptions from the typing module; now, doing so behaves as expected.typing.Generic superclass. That assumption only holds for standard generics and protocols; non-standard protocols subclassing non-typing superclasses (e.g., the abc.ABC superclass) after the list typing superclass in their method resolution order (MRO) flagrantly violate this assumption. Well, that's fine. We're fine with that. What's not fine about that? Fine. This is fine.beartype.typing.Protocol superclass leveraged the general-purpose @beartype._util.cache.utilcachecall.callable_cached decorator to memoize its subscription; however, since that decorator transitively imports from the beartype.typing subpackage, doing so induced a circular import dependency. To circumvent this, a new @beartype.typing._typingcache.callable_cached_minimal decorator implementing only the minimal subset of the full @beartype._util.cache.utilcachecall.callable_cached decorator has been defined; the beartype.typing subpackage now safely defers to this minimal variant for all its caching needs.@beartype decorator. For this and similar reasons, users are advised to begin refactoring their object-oriented codebases to decorate their classes rather than methods with @beartype.typing.ParamSpec objects by internally associating such objects with our beartype._data.hint.pep.sign.datapepsigns.HintSignParamSpec singleton, enabling @beartype to portably introspect Callable[typing.ParamSpec(...), ...] type hints.beartype.typing.Protocol compatibility. The @beartype-specific beartype.typing.Protocol superclass implementing PEP 544-compliant fast caching protocols is now fully compatible with mypy, Python's official static type-checker. Specifically, beartype.typing.Protocol now circumvents:
__slots__ as Any.typing.TypeVar() bounds parameter to this superclass.beartype.door.is_bearable() function and corresponding beartype.door.TypeHint.is_bearable() method are now annotated by the PEP 647-compliant typing.TypeGuard[...] type hint under both Python ≥ 3.10 and Python < 3.10 when the optional third-party typing_extensions dependency is installed. Doing so substantially reduces false positives from static type checkers on downstream codebases deferring to these callables. Thanks so much for improving @beartype so much, @justinchuby and @rsokl!@{classmethod,staticmethod,property} chaining. The @beartype decorator now implicitly supports callables decorated by both @beartype and one of the builtin method decorators @classmethod, @staticmethod, or @property regardless of decoration order, resolving issue #80 kindly requested by @qiujiangkun (AKA, Type Genius-kun). Previously, @beartype explicitly raised an exception when ordered after one of those builtin method decorators. This releseae relaxes this constraint, enabling callers to list @beartype either before or after one of those builtin method decorators.beartype.vale.Is[...] integration. Functional validators (i.e., beartype.vale.Is[...]) now integrate more cleanly with the remainder of the Python ecosystem, including:
linecache module now raise human-readable errors on type-checking, resolving issue #123 kindly submitted by typing brain-child @braniii. Relatedly, @beartype now permissively accepts both physical on-disk files and dynamic in-memory fake files cached with linecache as the files defining an arbitrary callable.bool object whose class defines at least one of the __bool__() or __len__() dunder methods and is thus implicitly convertible into a bool). Functional validators now support subscription by these functions, resolving issue #153 kindly submitted by molecular luminary @braniii (Daniel Nagel). Specifically, @beartype now unconditionally wraps all tester callables subscripting (indexing) beartype.vale.Is with a new private _is_valid_bool() closure that (in order):
bool values.bool values instead.beartype.cave.AsyncCoroutineCType.beartype.cave.AsyncGeneratorCType.beartype.cave.CallableCodeObjectType.beartype.cave.CallableFrameType.beartype.cave.ClassDictType.beartype.cave.ClassType.beartype.cave.ClosureVarCellType.beartype.cave.EllipsisType.beartype.cave.ExceptionTracebackType.beartype.cave.FunctionType.beartype.cave.FunctionOrMethodCType.beartype.cave.GeneratorCType.beartype.cave.MethodBoundInstanceDunderCType.beartype.cave.MethodBoundInstanceOrClassType.beartype.cave.MethodDecoratorBuiltinTypes.beartype.cave.MethodUnboundClassCType.beartype.cave.MethodUnboundInstanceDunderCType.beartype.cave.MethodUnboundInstanceNondunderCType.beartype.cave.MethodUnboundPropertyNontrivialCExtensionType.beartype.cave.MethodUnboundPropertyTrivialCExtensionType.beartype.cave deprecation removals. This release removes all deprecated third-party attributes from the beartype.cave submodule. The continued existence of these attributes substantially increased the cost of importing anything from our mostly undocumented beartype.cave submodule, rendering that submodule even less useful than it already is. Specifically, this release removes these previously deprecated attributes:
beartype.cave.NumpyArrayType.beartype.cave.NumpyScalarType.beartype.cave.SequenceOrNumpyArrayTypes.beartype.cave.SequenceMutableOrNumpyArrayTypes.beartype.cave.SetuptoolsVersionTypes.beartype.cave.VersionComparableTypes.beartype.cave.VersionTypes.beartype.roar.BeartypeCallHintViolation exceptions) raised by both @beartype-decorated callables and statement-level type-checkers (e.g., beartype.door.die_if_unbearable(), beartype.door.TypeHint.die_if_unbearable()), resolving issue #161 kindly submitted by foxy machine learning expert @justinchuby (Justin Chu). When standard output is attached to an interactive terminal (TTY), ANSII-flavoured colours now syntactically highlight various substrings of those violations for improved visibility, readability, and debuggability. Since all actively maintained versions of Windows (i.e., Windows ≥ 10) now widely support ANSII escape sequences across both Microsoft-managed terminals (e.g., Windows Terminal) and Microsoft-managed Integrated Development Environments (IDEs) (e.g., VSCode), this supports extends to Windows as well. The bad old days of non-standard behaviour are behind us all. Thanks so much to @justinchuby for his immense contribution to the righteous cause of eye-pleasing user experience (UX)!@beartype decorator, resolving issue #124 kindly submitted by typing brain-child @braniii. Thus was justice restored to the QAverse.beartype._decor._error.errormain.get_beartype_violation() getter from the parent type-checking wrapper function generated by the :mod:beartype.beartype decorator, resolving issue #140 kindly submitted by @langfield (William Blake – yes, that William Blake). That stack frame only needlessly complicated visual inspection of type-checking violations in tracebacks – especially from testing frameworks like :mod:pytest that recapitulate the full definition of the get_beartype_violation() getter (including verbose docstring) in those tracebacks. Specifically, this release:
raise_pep_call_exception() function to get_beartype_violation() for clarity.get_beartype_violation() to return rather than raise BeartypeCallHintViolation exceptions (while still raising all other types of unexpected exceptions for robustness).get_beartype_violation().None type. The type of the None singleton is no longer erroneously labelled as a PEP 544-compliant protocol in type-checking violations. Let's pretend that never happened.beartype.abby.die_if_unbearable() violations. The beartype.abby.die_if_unbearable() validator function no longer raises non-human-readable exception messages prefixed by the unexpected substring "@beartyped beartype.abby._abbytest._get_type_checker._die_if_unbearable() return". "Surely that never happened, @beartype!"beartype.door. @beartype now provides a new public framework for introspecting, sorting, and type-checking type hints at runtime in constant time. N-n-now... hear me out here. @leycec came up with a ludicrous acronym and we're going to have to learn to live with it: the Decidedly Object-Orientedly Recursive (DOOR) API. Or, beartype.door for short. Open the door to a whole new type-hinting world, everyone. beartype.door enables type hint arithmetic via an object-oriented type hint class hierarchy encapsulating the crude non-object-oriented type hint declarative API standardized by the typing module, resolving issues #133 and 138 kindly submitted by Harvard microscopist and general genius @tlambert03. The new beartype.door subpackage defines a public:
TypeHint({type_hint}) superclass, enabling rich comparisons between pairs of arbitrary type hints. Altogether, this class implements a partial ordering over the countably infinite set of all type hints. Pedagogical excitement ensues. Instances of this class efficiently satisfy both the collections.abc.Sequence and collections.abc.FrozenSet abstract base classes (ABC) and thus behave just like tuples and frozen sets over child type hints. Public attributes defined by this class include:
die_if_unbearable() and is_bearable() runtime type-checking methods, analogous in behaviour to the existing beartype.abby.die_if_unbearable() and beartype.abby.is_bearable() runtime type-checking functions.TypeHint.is_bearable(), currently implemented in terms of the procedural beartype.abby.is_bearable() tester.is_ignorable property evaluating to True only if the current type hint is semantically ignorable (e.g., object, typing.Any). There exist a countably infinite number of semantically ignorable type hints. The more you know, the less you want to read this changeset.==), enabling type hints to be compared according to semantic equivalence.<=, >), enabling type hints to be compared and sorted according to semantic narrowing.__bool__() dunder method, enabling type hint wrappers to be trivially evaluated as booleans according to the child type hints subscripting the wrapped type hints.__len__() dunder method, enabling type hint wrappers to be trivially sized according to the child type hints subscripting the wrapped type hints.__contains__() dunder method, enabling type hint wrappers to be tested for child type hint membership – just like builtin sets, frozen sets, and dictionaries.__getindex__() dunder method, enabling type hint wrappers to be subscripted by both positive and negative indices as well as slices of such indices – just like builtin tuples.beartype.door.AnnotatedTypeHint subclass.beartype.door.CallableTypeHint subclass.beartype.door.LiteralTypeHint subclass.beartype.door.NewTypeTypeHint subclass.beartype.door.TupleTypeHint subclass.beartype.door.TypeVarTypeHint subclass.beartype.door.UnionTypeHint subclass.is_subtype({type_hint_a}, {type_hint_b}) function, enabling @beartype users to decide whether any type hint is a subtype (i.e., narrower type hint) of any other type hint.beartype.roar.BeartypeDoorNonpepException type, raised when the beartype.door.TypeHint constructor is passed an object that is not a PEP-compliant type hint currently supported by the DOOR API.
Thanks so much to @tlambert03 for his phenomenal work here. He ran GitHub's PR gauntlet so that you did not have to. Praise be to him. Some people are the living embodiment of quality. @tlambert03 is one such people.beartype.peps. @beartype now publicizes runtime support for typing-centric Python Enhancement Proposals (PEPs) that currently lack official runtime support via a new public subpackage: beartype.peps. Notably, @beartype now provides:
beartype.peps.resolve_pep563() function resolving PEP 563-postponed type hints on behalf of third-party Python packages. This function is intended to be "the final word" on runtime resolution of PEP 563. May no other third-party package suffer as we have suffered. This commit is for you, everyone. And "by everyone," we of course mostly mean @wesselb of Plum fame. See also wesselb/plum#53.beartype.vale.Is*[...] {&,|} short-circuiting. &- and |-chained beartype validators now explicitly short-circuit when raising human-readable exceptions from type-checking violations against those validators, resolving issue #125 kindly submitted by typing brain-child @braniii.beartype.abby.is_bearable() when returning False. Previously, the public beartype.abby.is_bearable() runtime type-checker behaved reasonably optimally when the passed object satisfied the passed type hint but extremely suboptimally when that object violated that hint; this was due to our current naive implementation of that tester using the standard Easier to Ask for Permission than Forgiveness (EAFP) approach. This release fundamentally refactored beartype.abby.is_bearable() in terms of our new private beartype._check.checkmake.make_func_tester() type-checking tester function factory function. Ad-hoc profiling shows a speedup on the order of eight orders of magnitude – the single most intense optimization @beartype has ever brought to bear (heh). Our core code generation API now transparently generates both:
False on type-checking violations).int | str | None). Since these unions are non-self-caching type hints (i.e., hints that do not implicitly cache themselves to reduce space and time consumption), @beartype now efficiently coerces these unions into singletons in the same manner as PEP 585-compliant type hints – which are similarly non-self-caching.beartype.abby → beartype.door. This release officially deprecates the poorly named beartype.abby subpackage in favour of the sorta less poorly named beartype.door subpackage, whose name actually means something – even if that something is a punny acronym no one will ever find funny. Specifically:
beartype.abby.die_if_unbearable() has been moved to beartype.door.die_if_unbearable().beartype.abby.is_bearable() has been moved to beartype.door.is_bearable().
To preserve backward compatibility, the beartype.abby subpackage continues to dynamically exist (and thus be importable from) – albeit as a deprecated alias of the beartype.door subpackage.setuptools deprecation warning concerning the deprecated license_file setting in the top-level setup.cfg file. Next!typing.Protocol superclass and our caching beartype.typing.Protocol superclass.pyright. Notably:
test_pep561_pyright functional test statically type-checks the @beartype codebase against the external pyright command in the current ${PATH} (if available) specific to the version of the active Python interpreter currently being tested. For personal sanity, this test is currently ignored on remote continuous integration (CI) workflows. Let this shrieking demon finally die!beartype_test.util.cmd.pytcmdrun submodule underlying our cross-platform portable forking of testing subprocesses now transparently supports vanilla Windows shells (e.g., CMD.exe, PowerShell).beartype may now be fully tested from non-git repositories, including source tarballs containing the beartype_test package. Previously, three functional tests making inappropriate assumptions about the existence of a top-level .git/ directory failed when exercised from a source tarball.test_sphinx_build() functional test. This was surprisingly non-trivial – thanks to the pytest-specific sphinx.testing subpackage being mostly undocumented, behaving non-orthogonally, and suffering a host of unresolved issues that required we monkey-patch the core pathlib.Path class. Insanity, thy name is Sphinx.checkout@v3 and setup-python@v3 actions, inspired by a pair of sadly closed PRs by @RotekHandelsGmbH CTO @bitranox (Robert Nowotny). Thanks so much for the great idea, @bitranox!beartype.door conformance. A new smoke test guarantees conformance between our DOOR API and abstract base classes (ABCs) published by the standard typing module.beartype.abby documented. The new "Beartype At Any Time API" subsection of our front-facing README.rst file now documents our public beartype.abby API, resolving issue #139 kindly submitted by @gelatinouscube42 (i.e., the user whose username is the answer to the question: "What is the meaning of collagen sustainably harvested from animal body parts?").
GitHub Sponsors activated. @beartype is now proudly financially supported by GitHub Sponsors. Specifically, this release:
.github/FUNDING.yml).README.rst documentation.Sphinx configuration sanitized. As the first tentative step towards chain refactoring our documentation from its current monolithic home in our top-level README.rst file to its eventual modular home at ReadTheDocs (RTD), en-route to resolving issue #8 (!) kindly submitted a literal lifetime ago by visionary computer vision export and long-standing phenomenal Finn @felix-hilden (Felix Hildén):
autosectionlabels builtin Sphinx extension.doc/source/404.rst file has been temporarily moved aside, resolving a non-fatal warning pertaining to that file. Look, we're not here to actually solve deep issues; we're here to just get documentation building, which it's not. Sphinx, you have much to answer for.sphinx entry point now:
-n option previously passed to sphinx-build) due to Sphinx's autodoc extension locally failing to generate working references.(Impossible journey on an implacable placard-studded gurney!)
This patch release adumbrates with breathless support for mypy ≥ 0.940, the static type checker formerly known as "The Static Type Checker Whose Name
Beartype 0.10.4 released.
This patch release adumbrates with breathless support for mypy ≥ 0.940, the static type checker formerly known as "The Static Type Checker Whose Name Shall not Be Spoken."
This patch release resolves 5 issues and merges 0 pull requests. Noteworthy changes include:
beartype codebase now sports improved compatibility with the recently released mypy 0.94x series, which previously outted the @beartype decorator with a "Condition can't be inferred, unable to merge overloads [misc]" fatal error at static type-checking time. Specifically, this release fundamentally refactors (and in so doing mildly optimizes) our private beartype._decor.main submodule to leverage conditional overloads under the astute tutelage of mypy maestro @cdce8p; the @beartype.beartype decorator itself now resides in a new private beartype._decor.cache.cachedecor submodule, because obfuscation is the key to all successful open-source efforts. Doing so resolves issues #111 and #112 dual-reported concurrently by cutting-edge Microsoft luminary @daxpryce and German typing bad-ass @twoertwein.beartype codebase is now significantly faster when the definition of the @beartype.beartype decorator reduces to a noop (e.g., due to python3 -O optimization), partially resolving issue #94 kindly requested by the well-tanned and -toned typing star @matanster.beartype.abby under python3 -O. This release resolves an unreported critical defect in our new functional API (i.e., the pair of beartype.abby.is_bearable() and beartype.abby.die_if_unbearable() functions), which previously reduced to a noop when the @beartype.beartype decorator reduced to a noop (e.g., due to python3 -O optimization). By extricating the @beartype.beartype decorator into the beartype._decor.cache.cachedecor submodule (as described above), our functional API now directly defers to that decorator regardless of what the beartype package externally presents to third-party code.(Ironwrought irony untaught!)
This patch release positively vibrates with superlative support for functional beartype validators (i.e., beartype.vale.Is[...]).
Beartype 0.10.3 released.
This patch release positively vibrates with superlative support for functional beartype validators (i.e., beartype.vale.Is[...]).
This patch release resolves 5 issues and merges 2 pull requests. Noteworthy changes include:
## Issues Resolved
beartype.vale.Is[@beartype(...)].** The functional beartype validator API (i.e., the beartype.vale.Is[...] factory) now permissively accepts any low-level callable accepting one parameter wrapped by a higher-level callable, resolving issue #104 kindly submitted by the munificent typing maestro @dycw (Derek Wan). Specifically, the beartype.vale.Is[...] factory may now be subscripted (indexed) with any validation function wrapped by a decorator wrapper wrapped by the standard @functools.wraps decorator, including any @beartype-decorated validation function. Thus is the circle of validation complete. Cue Hakuna Matata.
`setup.py` circularity. Our top-level beartype.__init__ submodule no longer implicitly imports from any beartype submodule (except the guaranteeably safe beartype.meta submodule) when imported at install time by our root setup.py script, resolving issue #108 kindly discovered by @posita in a distressing comment embedded within the murky depths of PR #103. Specifically, setup.py now dynamically populates the standard sys.modules list with a fake beartype.__is_installing__ "module;" beartype.__init__ then detects the presence of that "module" and avoids implicitly importing from unsafe beartype submodules. In short: insane hackery. That's just how we roll.
## Tests Improved
Import isolation. The test_package_import_isolation() integration test minimizing importation costs by ensuring that the first import of the top-level lightweight beartype package does not accidentally import from one or more heavyweight third-party packages now omits the third-party typing_extensions module from scrutiny. Thanks a bundle of crypto that I do not have to @posita for his deep profiling sacrifice at #103! @posita: still dah best in 2022.
(Jubilant sibilance entranced a sigil's brilliance!)
This patch release serves up salacious support for improved compliance with [PEP 3102 -- Python Keyword-Only Parameters][PEP 3102].
Beartype 0.10.2 released.
This patch release serves up salacious support for improved compliance with PEP 3102 -- Python Keyword-Only Parameters.
This patch release resolves 1 issue and merges 0 pull request. <sup>it is sad</sup> Noteworthy changes include:
@beartype only supported optional keyword-only parameters strictly preceding mandatory keyword-only parameters in callable signatures. Now, @beartype supports callables whose optional and mandatory keyword-only parameters are heterogeneously mixed in any arbitrary order in callable signatures.Argument parsing. @beartype now parses callable signatures significantly faster, thanks to abandoning the extremely inefficient (albeit well-tested) inspect.signature() standard parser in favour of our extremely efficient (albeit not so well-tested) beartype._util.func.arg.utilfuncargiter.iter_func_args home-grown parser -- now believed to be the fastest pure-Python argument parser. Doing so significantly optimizes @beartype at decoration time. While still comparatively slow, the @beartype decorator is now within two orders of magnitude of the fastest possible decorator (i.e., noop identity decorator).
This patch release cooks up scrumptious support for fast [PEP 544 -- Protocols: Structural subtyping (static duck typing)][PEP 544] and improved compl
Beartype 0.10.1 released.
This patch release cooks up scrumptious support for fast PEP 544 -- Protocols: Structural subtyping (static duck typing) and improved compliance with PEP 570 -- Python Positional-Only Parameters.
This patch release resolves 3 issues and merges 1 pull request. Noteworthy changes include:
@beartype only supported positional-only parameters followed by one or more standard parameters in the signature of a callable. Now, @beartype unconditionally supports callables passed any permutation of positional-only and non-positional-only parameters– including callables passed only one or more positional-only parameters.beartype.typing.Protocol superclass has now been significantly optimized to internally cache structural subtyping checks across isinstance() calls the same protocol and objects of the same type checked against that protocol. Doing so ensures that:
@beartype-decorated callable annotated by one or more beartype.typing.protocol-style protocols will be as slow as equivalent calls to a @beartype-decorated callable annotated by one or more typing.Protocol-style protocols.typing.Protocol approach.This optimization is entirely thanks to @posita the dynamite ("He's TNT!") devop, who miraculously migrated this caching behaviour from his own in-house numerary.types implementation. Thunderous applause for this third out of three tremendous contributions! Likewise, thanks so much for the grand critique that helped fuel this in my awkward and questionable absence, @TeamSpen210.
You both are the stuff open-source dreams are made of. (Heated cleats!)
Consider resolving [PEP 585][PEP 585] deprecations by importing from our new beartype.typing API rather than the standard typing API. A battery of new…
Beartype 0.10.0 released.
This release titillates with scintillating support for PEP 557 -- Data Classes, PEP 570 -- Python Positional-Only Parameters, and PEP 604 -- Allow writing union types as X | Y.
This release resolves a bone-crushing 30 issues (mostly shameless dupes of one another, admittedly) and merges 3 pull requests. World-girdling changes include:
@beartype now supports dataclasses (i.e., types decorated by the standard @dataclasses.dataclass decorator), resolving issue #56 kindly submitted by @JulesGM (Jules Gagnon-Marchand) the Big Brain NLP researcher. Specifically, @beartype now transparently type-checks:
dataclasses.InitVar[...]).__init__() method generated by @dataclass for dataclasses through a clever one-liner employed by @antonagestam (Anton Agestam) the ageless Swede that I stan for.@beartype now supports positional-only arguments and no one cares. Given the triviality, the rear view mirror of regret suggests we kinda should've implemented this sooner. Better late than never, best @beartype friends for life (BBFFL).@beartype now supports new-style set unions (e.g., int | float), resolving issue #71 kindly submitted by pro typing aficionado Derek Wan (@dycw). Thanks to Derek for the helpful heads up that @beartype was headed straight for typing disaster under Python ≥ 3.10. Since we dodged another bullet there, this must mean we have now activated bullet time. Goooooo, slomo!typing.{Binary,Text,}IO[...] deep type-checking. @beartype now deeply type-checks subscripted typing.{Binary,Text,}IO[...] type hints, resolving issue #75 kindly submitted by Niklas "If I had a nickel for every lass..." Rosenstein. Notably:
typing.BinaryIO protocol and its typing.IO superclass share the exact same API, the typing.BinaryIO protocol is lamentably useless for all practical purposes. This protocol cannot be leveraged to detect binary file handles. Can binary file handles be detected at runtime then? Yes, we can! A binary file handle is any object satisfying the typing.IO protocol but not the typing.TextIO protocol. To implement this distinction, @beartype necessarily invented a novel form of type-checking and a new variant of type elision: anti-structural subtyping. Whereas structural subtyping checks that one class matches the API of another class (referred to as a "protocol"), anti-structural subtyping checks that one class does not match the API of another class (referred to as an "anti-protocol"). @beartype public exposes this functionality via the new beartype.vale.IsInstance[...] validator, enabling anyone to trivially perform anti-structural subtyping. In this case, @beartype internally reduces all useless typing.BinaryIO type hints to substantially more useful typing.Annotated[typing.IO, ~beartype.vale.IsInstance[typing.TextIO]] type hints.@beartype now supports untyped NumPy array type hints (i.e., the unsubscripted numpy.typing.NDArray and subscripted numpy.typing.NDArray[typing.Any] type hints), resolving issue #69 kindly submitted by @Jasha10, the stylish boy wonder dual-wielding the double thumbs-up and coke-bottle glasses that signify elementary genius. Specifically, this commit now detects and reduces these hints to the equivalent numpy.ndarray type.@beartype now squelches ignorable mypy complaints first introduced by mypy 0.920, including:
beartype now squelches implicit reexport complaints from mypy with respect to public attributes published by the beartype.cave subpackage, resolving issue #57 kindly reopened by Göteborg melodic death metal protégé and brightest academic luminary @antonagestam. This subpackage is now compatible with both the --no-implicit-reexport mypy CLI option and equivalent no_implicit_reexport = True configuration setting in .mypy.ini."# type: ignore[attr-defined]" pragma. Since mypy now ignores these pragmas, @beartype now silences its complaints through... unconventional means. A bear do wut a bear gotta do.beartype now publishes a new beartype.typing API as a typing compatibility layer improving forward compatibility with future Python releases, resolving issue #81 kindly submitted by the honorable @qiujiangkun (Qiu Jiangkun). Consider resolving PEP 585 deprecations by importing from our new beartype.typing API rather than the standard typing API. A battery of new unit tests ensure conformance:
beartype.typing and typing across all Python versions.beartype.typing.beartype package enabling end users to configure the @beartype decorator, including configuring alternative type-checking strategies other than constant-time runtime type-checking). Specifically, beartype now publishes:
beartype.BeartypeStrategy, an enumeration of all type-checking strategies to eventually be fully supported by future beartype releases – including:
BeartypeStrategy.O0, disabling type-checking for a callable by reducing @beartype to the identity decorator for that callable. Although currently useless, this strategy will usefully allow end users to selectively prevent callables from being type-checked by our as-yet-unimplemented import hook. When implemented, that hook will type-check all callables in a given package by default. Some means is needed to prevent that from happening for select callables. This is that means.BeartypeStrategy.O1, our default O(1) constant-time strategy type-checking a single randomly selected item of a container that you currently enjoy. Since this is the default, this strategy need not be explicitly configured. Of course, you're going to do that anyway, aren't you? </sigh>BeartypeStrategy.Ologn, a new O(lgn) logarithmic strategy type-checking a randomly selected number of items j of a container obj such that j = len(obj). This strategy is currently unimplemented (but will be implemented by a future beartype release).BeartypeStrategy.On, a new O(n) linear strategy deterministically type-checking all items of a container. This strategy is currently unimplemented (but will be implemented by a future beartype release).beartype.BeartypeConf, a simple dataclass encapsulating all flags, options, settings, and other metadata configuring the current decoration of the decorated callable or class. For efficiency, this dataclass internally self-caches itself (i.e., BeartypeConf(*args, **kwargs) is BeartypeConf(*args, **kwargs)). The __init__() method of this dataclass currently accepts these optional parameters:
is_debug boolean instance variable. When enabled, @beartype emits debugging information for the decorated callable – including the code for the wrapper function dynamically generated by @beartype that type-checks that callable.strategy instance variable whose value must be a BeartypeStrategy enumeration member. This is how you notify @beartype of which strategy to apply to each callable.is_debug parameter to the BeartypeConf.__init__ method significantly improves the debuggability of type-checking wrapper functions generated by @beartype. This configuration option is entirely thanks to @posita the positive Numenorean, who pined longingly for debuggable wrapper functions and now receives proportionately. Praise be to @posita! He makes bears better. Specifically, enabling this option enables developer-friendly logic like:
Pretty-printing to stdout (standard output) the definitions of those functions, including line number prefixes for readability.
Enabling those functions to be debugged. Thanks to a phenomenal pull request by the dynamic dual threat that is @posita + @TeamSpen210, @beartype now conditionally caches the bodies of type-checking wrapper functions with the standard (albeit poorly documented) linecache module. Thanks so much! Bear Clan 2022!!!
Suffixing the declarations of @beartype-specific hidden private "special" parameters passed to those functions with comments embedding their human-readable representations. Safely generating these comments consumes non-trivial wall clock at decoration time and is thus conditionally enabled for external callers requesting @beartype debugging. For example, note the "# is"-prefixed comments in the following signature of a @beartype-generated wrapper function for an asynchronous callable with signature async def control_the_car(said_the: Union[str, int], biggest_greenest_bat: Union[str, float]) -> Union[str, float]:
(line 0001) async def control_the_car(
(line 0002) *args,
(line 0003) __beartype_func=__beartype_func, # is <function test_decor_async_coroutine.<locals>.control_the_car at 0x7>
(line 0004) __beartype_raise_exception=__beartype_raise_exception, # is <function raise_pep_call_exception at 0x7fa13d>
(line 0005) __beartype_object_140328307018000=__beartype_object_140328307018000, # is (<class 'int'>, <class 'str'>)
(line 0006) __beartype_object_140328306652816=__beartype_object_140328306652816, # is (<class 'float'>, <class 'str'>)
(line 0007) **kwargs
(line 0008) ):
@beartype now supports two orthogonal modes of operation:
Decoration mode (i.e., the standard mode where @beartype directly decorates a callable without being passed parameters). In this mode, @beartype reverts to the default configuration of constant-time runtime type-checking and no debugging behaviour.
Configuration mode (i.e., the new mode where @beartype is called as a function passed a BeartypeConf object via the keyword-only conf parameter). In this mode, @beartype efficiently creates, caches, and returns a memoized decorator encapsulating the passed configuration: e.g.,
from beartype import beartype, BeartypeConf, BeartypeStrategy
@beartype(conf=BeartypeConf(strategy=BeartypeStrategy.On))
def muh_func(list_checked_in_linear_time: list[int]) -> int:
return len(list_checked_in_linear_time)
beartype now publishes a new beartype.vale.IsInstance[...] validator enforcing instancing of one or more classes, generalizing isinstanceable type hints (i.e., normal pure-Python or C-based classes that can be passed as the second parameter to the isinstance() builtin). Unlike standard isinstanceable type hints, beartype.vale.IsInstance[...] supports various set theoretic operators. Critically, this includes negation. Instance validators prefixed by the negation operator ~ match all objects that are not instances of the classes subscripting those validators. Wait. Wait just a hot minute there. Doesn't a typing.Annotated_ type hint necessarily match instances of the class subscripting that type hint? Yup. This means type hints of the form typing.Annotated[{superclass}, ~IsInstance[{subclass}] match all instances of a superclass that are not also instances of a subclass. And... pretty sure we just invented type hint arithmetic right there. That sounded intellectual and thus boring. Yet, the disturbing fact that Python booleans are integers <sup>yup</sup> while Python strings are infinitely recursive sequences of strings <sup>yup</sup> means that type hint arithmetic can save your codebase from Guido's younger self. Consider this instance validator matching only non-boolean integers, which cannot be expressed with any isinstanceable type hint (e.g., int) or other combination of standard off-the-shelf type hints (e.g., unions): Annotated[int, ~IsInstance[bool]]. ← bruhbeartype now publishes a new public beartype.abby subpackage enabling users to type-check anything anytime against any PEP-compliant type hints, resolving feature request #79 kindly submitted by (...wait for it) typing Kung Fu master @qiujiangkun (Qiu Jiangkun). This subpackage is largely thanks to @qiujiangkuni, whose impeccable code snippets drive our initial implementation. This subpackage provides these utility functions:
beartype.abby.is_bearable(), strictly returning a boolean signifying whether the passed arbitrary object satisfies the passed type hint or not (e.g., is_bearable(['the', 'centre', 'cannot', 'hold;'], list[int]) is False).beartype.abby.die_if_unbearable(), raising the new beartype.roar.BeartypeAbbyHintViolation exception when the passed arbitrary object violates the passed type hint.@beartype now raises instructive exceptions when decorating an uncallable descriptor created by a builtin decorator (i.e., @property, @classmethod, @staticmethod) due to the caller incorrectly ordering @beartype above rather than below that decorator, resolving issue #80 kindly submitted by typing academician @qiujiangkun (Qiu Jiangkun). Specifically, @beartype now raises human-readable exceptions suffixed by examples instructing callers to reverse decoration ordering.@beartype now appends a detailed pretty-printed diagnosis of how any object either satisfies or fails to satisfy any beartype validator to exception messages raised by high-level validators synthesized from lower-level validators (e.g., via overloaded set theoretic operators like |, &, and ~), resolving issue #72 kindly submitted by the unwreckable type-hinting guru Derek Wan (@dycw). This diagnostic trivializes validation failures in non-trivial use cases involving multiple nested conjunctions, disjunctions, and/or negations.@beartype call-time performance. @beartype now generates faster type-checking wrapper functions with a vast and undocumented arsenal of absolutely "legal" weaponry, including:
typing.{Generic,Protocol} deduplication. @beartype now microoptimizes away redundant isinstance() checks in wrapper functions checking @beartype-decorated callables annotated by PEP 484-compliant subgenerics or PEP 585-compliant subprotocols (i.e., user-defined classes subclassing user-defined classes subclassing typing.{Generic, Protocol}), resolving issue #76 kindly submitted by @posita the positive numerics QA guru and restoring the third-party numerary package to its glory. Our generics workflow has been refactored from the ground-up to stop behaving insane. @beartype now performs an inner breadth-first search (BFS) across generic pseudo-superclasses in its existing outer BFS that generates type-checking code. When you're nesting a BFS-in-a-BFS, your code went full-send. There's no going back from that.@beartype now resolves a performance regression in type-checking wrapper functions passed worst-case nested data structures violating PEP-compliant type hints, resolving issue #91 kindly submitted by Cuban type-checking revolutionary @mvaled (Manuel Vázquez Acosta). Specifically, this commit safeguards our low-level represent_object() function stringifying objects embedded in exception messages describing type-checking violations against worst-case behaviour. A new unit test shieldwalls against further performance regressions. All our gratitude to @mvaled for unveiling the darkness in the bear's heart.@beartype decoration-time performance. The @beartype decorator has been restored to its prior speed, resolving performance regressions present throughout our [0.8.0, 0.10.0) release cycles. Significant decoration-time optimizations include:
@beartype now directly accesses the code object underlying the possibly unwrapped callable being decorated via a temporary cache rather than indirectly accessing that code object by repeatedly (and expensively) unwrapping that callable, dramatically optimizing low-level utility functions operating on code objects.@beartype now defers calling expensive exception handling-specific functions until an exception is raised, dramatically restoring our decoration-time performance to the pre-0.8.0 era – which isn't that great, honestly. But we'll take anything. Substantial optimizations remain, but we are dog-tired. Moreover, DQXIS:EofaEA (...that's some catchy name right there) ain't gonna play itself – OR IS IT!?! Cue creepy AI.@beartype now internally lelaxes inapplicable safety measures previously imposed by our internal FixedList container type. Notably, this type previously detected erroneous attempts to extend the length of a fixed list by subversively assigning a slice of that fixed list to a container whose length differs from that of that slice. While advisable in theory, @beartype never actually sliced any fixed list -- let alone used such a slice as the left-hand side (LHS) of an assignment. Disabling this detection measurably improves the efficiency of fixed lists across the codebase -- which is, after all, the entire raison d'etre for fixed lists in the first place. </shaking_my_head>@beartype now introspects callable signatures using a homegrown lightweight parameter parsing API. @beartype previously introspected signatures using the standard heavyweight inspect module, which proved... inadvisable. All references to that module have been removed from timing-critical code paths. All remaining references reside only in timing-agnostic code paths (e.g., raising human-readable exceptions for beartype validators defined as anonymous lambda functions).@beartype importation-time performance. The beartype package now avoids unconditionally importing optional first- and third-party subpackages, improving the efficiency of the initial from beartype import beartype statement in particular. beartype now intentionally defers these imports from global module scope to the local callable scope that requires them. A new functional test guarantees this to be the case.beartype 0.1.0. This includes:
beartype.roar.BeartypeCallHintPepException, deprecated by beartype.roar.BeartypeCallHintViolation.beartype.roar.BeartypeCallHintPepParamException, deprecated by beartype.roar.BeartypeCallHintParamViolation.beartype.roar.BeartypeCallHintPepReturnException, deprecated by beartype.roar.BeartypeCallHintReturnViolation.The Frequently Asked Questions (FAQ) section of our front-facing README.rst documentation now sports a medley of new entries, including instructions on:
bearboto3, Boto3 @beartype bindings by (wait for it) @paulhutchings.The hype train is now boarding. All aboooooooard! (Classless masterless masterclass!)
This patch release delivers obstreperous support for Sphinx (*curse ye and yer little side effects too, autodoc extension!*), generator callables, rea
Beartype 0.9.1 released.
This patch release delivers obstreperous support for Sphinx (curse ye and yer little side effects too, autodoc extension!), generator callables, readable exception messages, and Python 3.10 CI.
This release resolves 4 issues and merges 0 pull requests. Fearsome changes include:
@beartype now explicitly provides first-class support for Sphinx's autodoc extension (i.e., sphinx.ext.autodoc), resolving issue #61 kindly submitted by SeldonIO/alibi ML dev maestro supremum Janis Klaise (@jklaise). @beartype now transparently ignores autodoc-mocked module attributes (e.g., produced by the autodoc_mock_imports list global in Sphinx-specific doc{s,}/conf.py configuration files of downstream documentation trees) used as type hints annotating @beartype-decorated callables, rendering @beartype compatible with autodoc-documented codebases. Since Sphinx lacks a public API for detecting when autodoc is currently generating documentation (see: sphinx-doc/sphinx#9805), @beartype now performs its own ad-hoc detection at decoration time with micro-optimized runtime complexity:
O(1) in the common case (i.e., Sphinx's autodoc extension has not been previously imported under the active Python interpreter).O(n) in the worst case (i.e., Sphinx's autodoc extension has been previously imported under the active Python interpreter), where n is the height of the call stack leading to @beartype. Thanks, Sphinx. Thanks alot.@beartype now relaxes the requirement that [a]synchronous generator callables be annotated by PEP 484 -compliant typing.{Async,}Generator[...] or PEP 585-compliant collections.abc.{Async,}Generator[...] type hints to additionally accept PEP 484-compliant typing.{Async,}Iterable[...] and typing.{Async,}Iterator[...] as well as PEP 585-compliant collections.abc.{Async,}Iterable[...] and collections.abc.{Async,}Iterator[...] type hints, resolving issue #65 kindly submitted by the @posita the positronic brain emitter.@beartype now emits more human-readable, disambiguous, and useful exception messages or my middle name isn't "Brain Trust." Notably:
@beartype-decorated callables annotated by one or more numeric types (e.g., int, float, complex) now raise exceptions on type-checking failures whose exception messages disambiguate those numbers from strings, resolving issue #63 kindly submitted by @jefcolbi. Previously, these messages double-quoted all numbers for disambiguity with the possibly preceding sequence index of those numbers in their parent containers – introducing yet another ambiguity between numbers and strings. Numbers are now preserve in their vanilla unquoted form; meanwhile, sequence items are additionally disambiguated from sequence values with additional explanatory substrings."type hint".Reverse dependency showcase. The introduction to our front-facing README.rst documentation now gratefully advertises popular reverse dependencies (i.e., downstream open-source Python projects directly leveraging beartype) with a new on-brand icon bar. Gaze upon iconic icons and know the stylish face of unabashed beauty.
Type hint elision. The new "Type Hint Connectives" subsection of our front-facing README.rst documentation documents various means of circumventing counter-intuitive historicity in Python's core type hierarchy with (wait for it) beartype validators, resolving issue #62 kindly submitted by Cal Leeming (@foxx). Specifically, this subsection documents how to effectively declare:
int - bool type hint as a beartype validator matching the set of all integers that are not booleans.Sequence - str type hint as a beartype validator matching the set of all sequences that are not strings.(Unleaded laden jelly-like underbellies!)
Emits more self-explanatory deprecation warnings for [PEP 484][PEP 484]-compliant type hints deprecated by [PEP 585][PEP 585] under Python ≥ 3.9. Spec…
Beartype 0.9.0 released.
This release adds voluminous support for asynchronous callables (including both coroutines and asynchronous generators) while improving existing support for typed NumPy arrays (i.e., numpy.typed.NDArray type hints), beartype validators (i.e., beartype.vale type hints), PEP 484, PEP 563, PEP 585, PEP 586, PEP 589, and PEP 593.
This release resolves 6 issues and merges 2 pull requests. Non-cringe-worthy changes include:
@beartype now transparently supports coroutines (i.e., callables declared with async def rather than merely def) with the exact same shallow and deep type-checking semantics as standard synchronous callables. Notably, @beartype now accepts all valid variants of coroutine type hints standardized by PEP 484, PEP 585, and mypy. This includes:
async def coroutine(...) -> {return}, which @beartype now wraps with an asynchronous coroutine first awaiting the decorated coroutine() and then validating the value returned by coroutine() satisfies the {return} type hint.async def coroutine(...) -> typing.Coroutine[{yield}, {send}, {return}], which @beartype now wraps with an asynchronous coroutine first awaiting the decorated coroutine() and then validating the value returned by coroutine() satisfies the {return} type hint (while continuing to silently ignore the {yield} and {send} child type hints).@beartype now transparently supports asynchronous generators (i.e., generators declared with async def rather than merely def) with the exact same shallow and deep type-checking semantics as standard synchronous generators. Notably, @beartype now requires the returns of:
beartype 0.9.0 refactors the entire beartype.vale class hierarchy to leverage the widely supported __getitem__() dunder method supported by Python ≥ 3.6 rather than the PEP 560-compliant __class_getitem__() dunder method supported only under Python ≥ 3.8 and not supported by mypy. Naturally, this was pain.@beartype now accepts all valid variants of numpy.typing.NDArray[{dtype}] type hints also accepted by mypy, where {dtype} is either:
numpy.typing.NDArray[numpy.dtype(numpy.float64)]).numpy.typing.NDArray[numpy.float64]).numpy.typing.NDArray[numpy.floating]).
Previously, @beartype rejected scalar NumPy ABCs. Since @beartype now accepts these ABCs, the most portable means of type-checking NumPy arrays of arbitrary precision is to subscript numpy.typing.NDArray by the appropriate scalar ABC: e.g.,numpy.typing.NDArray[numpy.floating] rather than numpy.typing.NDArray[numpy.float64], matching any floating-point NumPy array (regardless of precision).numpy.typing.NDArray[numpy.integer] rather than numpy.typing.NDArray[numpy.int64]., matching any integer NumPy array (regardless of precision).
Lastly, @beartype now supports these hints across all Python versions. Under Python ≥ 3.9, this support works as expected out-of-the-box. Under Python 3.6, 3.7, and 3.8:typing_extensions package is also importable, @beartype now deeply type-checks these hints as expected.@beartype now only shallowly type-checks these hints by internally reducing all numpy.typing.NDArray[{dtype}] type hints to the untyped NumPy array class numpy.ndarray. Since this is admittedly non-ideal, @beartype now emits one non-fatal warning of category beartype.roar.BeartypeDecorHintNonpepNumpyWarning at decoration time for each such reduction. Don't blame us. We voted for Kodos. How could this be our fault!?!? <sup>it's totally our fault</sup>@beartype now:
typing.Type type hints, validating parameters and returns to be subclasses of the subscripted type. This includes all syntactic variants standardized by PEP 484:
typing.Type, matching any issubclassable type (i.e., normal class passable as the second parameter to the issubclass() builtin).typing.Type[Any], also matching any issubclassable type.typing.Type[{type}], matching both the issubclassable type {type} and any subclass of that type.typing.Type[{forward_ref}], first dynamically resolving the forward reference {forward_ref}' to an issubclassable type {type}` at call time and then matching both that type and any subclass of that type.typing.Type[typing.Union[{type1}, {type2}, ..., {typeN}], permissively matching the issubclassable types {type1}, {type2}, and {typeN} as well as any subclass of those types.typing.Coroutine type hints, validating callables to be coroutines. Given a type hint typing.Coroutine[{yield}, {send}, {return}], @beartype now deeply type-checks the {return} child type hint (while continuing to silently ignore the {yield} and {send} child type hints).typing.TypeVar instances instantiated with either two or more constraints or one upper bound). @beartype continues to silently ignore unparametrized type variables (i.e., type variables instantiated with neither constraints nor upper bounds). Notably, @beartype now shallowly type-checks any type variable instantiated with:
typing.TypeVar('T', str, bytes)) as a union of those positional parameters instead (e.g., as typing.Union[str, bytes]).bound keyword parameter (e.g., typing.TypeVar('T', bound=float)) as that upper bound instead (e.g., as float).README.rst documentation.beartype.roar.BeartypeDecorHintPepDeprecatedWarning class has been refined into a new beartype.roar.BeartypeDecorHintPep484DeprecationWarning class specific to this category of deprecation, enabling downstream consumers (this means you) to selectively ignore only this category of deprecation.@beartype now:
from __future__ import annotation pragma.@beartype now:
type type hints, exactly as described for PEP 484-compliant typing.Type type hints above.collections.abc.Coroutine type hints, exactly as described for PEP 484-compliant typing.Coroutine type hints above.List[str]) and indeed most other PEP-compliant type hints are already effectively deduplicated by caching hidden in the standard typing module (e.g., List[str] is List[str]). Despite deprecating PEP 484, PEP 585 fails to deduplicate its hints (e.g., list[str] is not list[str]). @beartype now internally deduplicates duplicated PEP 585-compliant type hints at decoration time via a thread-safe cache from the machine-readable string representations of such hints to such hints.Literal). @beartype now transparently supports both the official PEP 586-compliant typing.Literal type hint and its quasi-official typing_extensions.Literal backport to older Python versions. Thanks to cutting-edge Microsoft luminary @pbourke for his detailed assessment and resolution of everything that wrong with @beartype.TypedDict). @beartype now shallowly type-checks typed dictionaries (i.e., both typing.TypedDict type hints under Python ≥ 3.8 and typing_extensions.TypedDict type hints under Python < 3.8) by internally reducing these hints to simply Mapping[str, object]. Doing so was surprisingly non-trivial, as the Python 3.8-specific implementation of the typing.TypedDict subclass is functionally deficient and (presumably) never meaningfully unit-tested. It's not a good look.Annotated). @beartype now transparently supports both the official PEP 593-compliant typing.Annotated type hint and its quasi-official typing_extensions.Annotated backport to older Python versions. Thanks to cutting-edge Microsoft luminary @pbourke for his detailed assessment and resolution of everything that wrong with @beartype... yet again.beartype.vale.IsSubclass[{type}] beartype validator validates arbitrary objects and object attributes to be subclasses of the superclass {type}. Whereas the comparable PEP 484-compliant typing.Type[{type}] and PEP 585-compliant type[{type}] type hints validate the same semantics only on @beartype-decorated callable parameters and returns annotated by those hints, beartype.vale.IsSubclass[{type}] validates those semantics on any objects reachable with beartype validators – including arbitrary deeply nested attributes of when coupled with the existing beartype.vale.IsAttr[{attr_name}, {validator}] beartype validator. In fact, @beartype internally reduces all typed NumPy arrays subscripted by scalar NumPy ABCs** (e.g., numpy.typing.NDArray[numpy.floating]) to semantically equivalent beartype validators (e.g., typing.Annotated[numpy.ndarray, beartype.vale.IsAttr['dtype', beartype.vale.IsAttr['type', beartype.vale.IsSubclass[numpy.floating]]]]).beartype 0.9.0 deprecates decrepit relics of a long-forgotten past littering the beartype codebase with unseemly monoliths to human hubris. Specifically, importing these deprecated attributes under beartype ≥ 9.0 now emits non-fatal DeprecationWarning warnings at runtime:
beartype.cave.HintPep585Type, which should now be accessed as the non-deprecated beartype.cave.HintGenericSubscriptedType attribute.beartype.cave.NumpyArrayType, which should now be accessed directly as numpy.ndarray.beartype.cave.NumpyScalarType, which should now be accessed directly as numpy.generic.beartype.cave.SequenceOrNumpyArrayTypes, which should now be annotated as typing.Union[collections.abc.Sequence, numpy.ndarray].beartype.cave.SequenceMutableOrNumpyArrayTypes, which should now be annotated directly as
typing.Union[collections.abc.MutableSequence, numpy.ndarray].beartype.cave.SetuptoolsVersionTypes, which should now be accessed directly as packaging.version.Version.beartype.cave.VersionComparableTypes, which should now be annotated directly as typing.Union[tuple, packaging.version.Version].beartype.cave.VersionTypes, which should now be annotated directly as typing.Union[str, tuple, packaging.version.Version].beartype.roar.BeartypeDecorHintNonPepException, which should now be accessed as the non-deprecated
beartype.roar.BeartypeDecorHintNonpepException attribute.beartype.roar.BeartypeDecorHintNonPepNumPyException, which should now be accessed as the non-deprecated beartype.roar.BeartypeDecorHintNonpepNumpyException` attribute.beartype.roar.BeartypeDecorHintPepDeprecatedWarning, which should now be accessed as the non-deprecated beartype.roar.BeartypeDecorHintPepDeprecationWarning` attribute.numpy.typing.NDArray[numpy.floating]), resolving issue #48 kindly submitted by @braniii the bran-eating brainiac. See above for details.typing_extensions.Literal and PEP 593 typing_extensions.Annotated backports, resolving issue #52 kindly submitted by cutting-edge Microsoft graph luminary @pbourke. See above for details.TypedDict), resolving issue #55 kindly submitted by Kyle (@KyleKing), the undisputed King of Parexel AI Labs.beartype 0.9.0 is now compatible with both the --no-implicit-reexport mypy CLI option and equivalent no_implicit_reexport = True configuration setting in .mypy.ini, resolving issue #57 kindly submitted by Göteborg melodic death metal protégé and assumed academic luminary @antonagestam. Specifically, beartype now internally reexports all exception and warning classes in the private beartype.roar.__init__ submodule under... their exact same names. Look. We don't make the rules. We just circumvent them.beartype 0.9.0 resolves a long-standing integration issue when subjecting beartype to static type checking by a static type checker that is almost certainly mypy. Previously, mypy erroneously emitted one false positive for each otherwise valid beartype validator (e.g., error: The type "Type[IsAttr]" is not generic and not indexable [misc]). Mypy and presumably other static type checkers now no longer do so, substantially improving the usability of downstream packages leveraging both static type checking and beartype validators.test_doc_readme() unit test non-portably assumed UTF-8 to be the default file encoding under all platforms and thus loudly failed under Windows, which still defaults to the single-byte encoding cp1252. Thanks to @pbourke and the valiant team at microsoft/graspologic, the logical statistical graph grasper.Asynchronous testing. Our new beartype_test.conftest pytest plugin effectively implements the useful subset of the mostly unmaintained, overly obfuscatory, and poorly commented and documented pytest-asyncio project -- which, unsurprisingly, has an outrageous number of unresolved issues and unmerged pull requests. Thus dies another prospective mandatory test dependency. We now give thanks.
Git-agnostic testing. The beartype test suite has been generalized to support testing from arbitrary directory layouts, including testing of both local clones of our remote git repository and local extractions of our remote PyPI- and GitHub-hosted source tarballs. Previously, our test suite only supported the former – due to bad assumptions that will haunt our issue tracker like the stench of uncooked salmon on the banks of Bear River.
PyPy 3.6 testing support dropped. beartype 0.9.0 now circumvents obscure non-human-readable exceptions raised by the macOS-specific implementation of PyPy 3.6 when testing under GitHub Actions-based continuous integration (CI), resolving pypy/pypy#3314. Since Python 3.6 has almost hit its official End of Life (EoL) anyway, we've chosen the Easy Road: unconditionally omit PyPy 3.6 from testing and pretend this never happened. You didn't see nuffin'.
(Reticulated ticks gesticulate; ant antics masticate!)
This minor release resolves a significant edge case with typed NumPy arrays (i.e., numpy.typed.NDArray type hints). This release resolves 1 issue – al
Beartype 0.8.1 released.
This minor release resolves a significant edge case with typed NumPy arrays (i.e., numpy.typed.NDArray type hints). This release resolves 1 issue – albeit a significant issue for NumPy users. Changes include:
@beartype now generates syntactically valid type-checking for typed NumPy arrays nested in beartype validators nested in fixed-length tuple type hints (e.g., Tuple[ Annotated[NDArray[np.floating], Is[lambda arr: arr.ndim == 1]], Annotated[NDArray[np.floating], Is[lambda arr: arr.ndim == 1]]]). Previously, @beartype erroneously chained assignment expressions sans parens protection. @beartype now avoids chaining assignment expressions altogether.Interestingly, whereas chained assignments are syntactically valid, chained assignment expressions are syntactically invalid unless protected with parens under Python ≥ 3.8:
>>> a = b = 'Mother*Teacher*Destroyer' # <-- fine
>>> (a := "Mother's Abomination") # <-- fine
>>> (a := (b := "Mother's Illumination")) # <-- fine
>>> (a := b := "Mother's Illumination") # <-- not fine
SyntaxError: invalid syntax
The more you know, the less you know. Thanks again to @braniii for all the vigorous enthusiasm and brain-melting subtleties. You made data science a safer place for us. (Elucidative sedatives redact acidity!)
This release brings undeniable support for typed NumPy arrays (i.e., numpy.typed.NDArray type hints), typing backports (i.e., public public attributes
Beartype 0.8.0 released.
This release brings undeniable support for typed NumPy arrays (i.e., numpy.typed.NDArray type hints), typing backports (i.e., public public attributes of the third-party typing_extensions package, enabling typing_ types introduced by newer Python versions to be used under older Python versions), and portable beartype validators (i.e., beartype.vale type hints usable under all Python versions via the typing_extensions.Annotated backport). This release resolves 4 issues and merges 2 pull requests. Changes include:
numpy.typed.NDArray type hints). The @beartype decorator now transparently supports the third-party numpy.typing.NDArray type hint newly introduced by NumPy ≥ 1.21.0, resolving issue #42 kindly submitted by NumPy extraordinaire @Antyos. Note that usage of typed NumPy arrays under Python < 3.9.0 requires installation of the third-party typing-extensions package, which @beartype will then automagically detect and leverage internally.typing_extensions type hints). The @beartype decorator now transparently supports all public type hints of the third-party typing_extensions package, resolving issue #34 kindly submitted by Ryan Soklaski (@rsokl) of considerable MIT Beaver Works Summer Institute and Python Like You Mean It fame.@beartype decorator now portably supports beartype validators (i.e., beartype.vale objects annotating typing{_extensions}.Annotated type hints) across all Python versions, also resolving issue #34 kindly submitted by Ryan Soklaski (@rsokl). <sup>hallowed be his username</sup> Note that usage of beartype validators under Python < 3.9.0 requires:
typing-extensions package.beartype.vale objects by typing_extensions.Annotated (rather than typing.Annotated).typing.NewType type hints. This release resolves a minor incompatibility recently introduced by Python 3.10.0rc1, which (waitforit) broke backward compatibility with prior implementations of the public typing.NewType type hint. Previously, that hint was implemented as a closure; Python ≥ 3.10 fundamentally refactored that hint to a pure-Python class instance – significantly complicating cross-version detection. yuwastemytime, CPython?@beartype adopted mypy's permissive approach of implicitly coercing the return annotations of binary dunder methods returning only booleans to instead return typing.Union[bool, type(NotImplemented)]. beartype now expands that approach to all binary dunder methods regardless of return annotation. Thanks to pull request #40 from Matt Bogosian (@posita), Dropbox's positive code genius!typing_extensions.Annotated backport.@beartype incorrectly generated syntactically invalid code for decorated callables annotated by one or more fake builtin types – which, miraculously, there are hardly any of. Still... it's not a good look, beartype. Thanks to Matt Bogosian (@posita), Dropbox's positive code genius, for first detecting this horrifying edge case in pull request #40.typing_extensions.Annotated backport.README.rst documentation now provides a post-installation advisory suggesting public declaration of @beartype project compatibility with reST- and Markdown-friendly text exhibiting a beautiful @beartype badge. Unsurprisingly, @posita made that too... because what doesn't @posita do? Nuthin'! I'm pretty sure that means @posita does everything.Optional dependencies. Our GitHub Actions-based continuous integration (CI) configuration now installs optional test-time dependencies. Although beartype has no mandatory runtime dependencies, fully exercising all tests necessitates installing these test-time dependencies:
numpy.typing.NDArray type hints. This proved surprisingly non-trivial. Apple's patently broken "Accelerate" BLAS replacement (as documented at numpy/numpy#15947) blocks NumPy ≥ 1.18.0 installation under default CI configurations, necessitating that we:
pip packages with --force-reinstall under macOS. CI gonna CI.--force-reinstall option fails to improve matters. You can only do so much when your core platform is fundamentally broken. Thanks, Apple.typing_extensions, enabling tests for typing attributes unavailable under the active Python interpreter.Thanks yet again to Badge Connoisseur @posita for his tremendous efforts – which we are now eternally indebted to and can only repay across the span of several gruelling lifetimes. "It will all be worth it," I tell my future self.
(Clogged clogs and unlogged cogs!)
This release delivers eleventh-hour support for the NotImplemented singleton and improved compliance with static analysis tooling like static type-che
This release delivers eleventh-hour support for the NotImplemented singleton and improved compliance with static analysis tooling like static type-checkers (e.g., mypy) and intelligent IDEs (e.g., not Vim). This release resolves 4 outstanding issues and merges 1 pending pull request.
Changes include:
NotImplemented singleton. @beartype now explicitly supports the builtin NotImplemented singleton with the same behaviour implemented by mypy to support this singleton, resolving issue #38 kindly reported by Matt the positive @posita. Specifically, @beartype now implicitly coerces binary dunder methods (e.g., __eq__()) annotated as returning booleans to instead be annotated as returning either booleans or the NotImplemented singleton (e.g., from def __eq__(self, other: object) -> bool to def __eq__(self, other: object) -> Union[bool, type(NotImplemented)]).@beartype now better complies with static analysis performed by both static type-checkers (e.g., mypy) and intelligent IDEs (e.g., not Vim), resolving both issues #36 (kindly reported by @jonathanmorley the majestic code maestro) and #39 (again kindly reported by Matt the positive @posita). Specifically, the @beartype decorator itself is now annotated as returning a callable having the exact same signature as the passed callable. Previously, @beartype was annotated as merely returning a callable of arbitrary signature. Thanks again to @jonathanmorley the majestic code maestro for both the initial issue and pull request resolving that issue!(Frenetic phrenology eulogizes occipital antics!)
This release brings titillating support for [beartype validators][beartype validators], Python 3.10, [PEP 563 – "Postponed Evaluation of Annotations"]
Beartype 0.7.0 released.
This release brings titillating support for beartype validators, Python 3.10, PEP 563 – "Postponed Evaluation of Annotations", and PEP 586 – "Literal Types".
This release resolves 4 outstanding issues and merges 1 pending pull request. Significant changes include:
@beartype decorator for you. The new public beartype.vale subpackage enables beartype users to design their own PEP-compliant type hints enforcing arbitrary runtime constraints on the internal structure and contents of parameters and returns via user-defined lambda functions and nestable declarative expressions leveraging familiar typing syntax – all seamlessly composable with standard type hints through an expressive domain-specific language (DSL). Specifically, @beartype-decorated callables may now be annotated by type hints of the form typing.Annotated[{cls}, beartype.vale.Is[lambda obj: {test_expr1}], ..., beartype.vale.Is[lambda obj: {test_exprN}]], where:
{cls} is any arbitrary class (e.g., str, numpy.ndarray).{test_expr1} and {test_exprN} are any arbitrary expressions evaluating to booleans (e.g., len(obj) <= 80, obj.dtype == np.dtype(np.float64)).
beartype.vale.Is may also be subscripted (indexed) by non-lambda callables with similar signatures. For convenience, beartype.vale.Is objects support a rich domain-specific language (DSL) enabling new validators to be synthesized from existing validators with Pythonic set operators:~beartype.vale.Is[lambda obj: {test_expr}], equivalent to
beartype.vale.Is[lambda obj: not {test_expr}].beartype.vale.Is[lambda obj: {test_expr1}] & beartype.vale.Is[lambda obj: {test_expr2}], equivalent to beartype.vale.Is[lambda obj: {test_expr1} and {test_expr2}].beartype.vale.Is[lambda obj: {test_expr1}] | beartype.vale.Is[lambda obj: {test_expr2}], equivalent to beartype.vale.Is[lambda obj: {test_expr1} or {test_expr2}].
This syntax fully complies with PEP 593 and thus requires Python ≥ 3.9. See Beartype validators for full usage instructions, complete with real-world examples including tensors. Rejoice machine learning data scientists! This resolves issue #32, kindly submitted by fashionable London steampunk cat pimp @Saphyel (Carlos Jimenez).beartype package and @beartype decorator has been significantly optimized, now consuming on the order of microseconds rather than milliseconds (or even seconds in the worst case). This critical optimization should significantly improve runtime performance for short-lived CLI applications. Isn't that great, guys? ...guys? awkward cough@beartype decorator now generates unconditionally faster type-checking wrapper functions. Previously, attributes accessed in the bodies of those functions were indirectly passed to those functions via a common dictionary singleton referred to as the "beartypistry" directly passed to those functions; while trivial, this approach had the measurable harm of one dictionary lookup for each attribute access in those functions. Now, the same attributes are instead directly passed as optional private beartype-specific parameters to these functions; while non-trivial, this approach has the measurable benefit of avoiding any dictionary lookups by instead localizing all requisite attributes to the signatures of those functions. Of course, this isn't just an optimization; this is also a hard prerequisite for supporting both "PEP 586 -- Literal Types" and beartype validators. The beartypistry singleton remains used only to dynamically resolve forward references to undeclared user types.@beartype now officially supports Python 3.10, currently in beta but maybe-soon-to-be-released thanks to Python's accelerated release schedule. Python 3.10 significantly broke backwards compatibility with runtime introspection of type hints and thus runtime type checkers, complicating support for Python 3.10 for most runtime type checkers (including us). Specifically, Python 3.10 unconditionally enables "PEP 563 -- Postponed Evaluation of Annotations" – an abysmal standard preferentially improving the efficiency of statically type-checked applications by reducing the efficiency of applications also checked by runtime type checkers. We can only protest with skinny fists lifted like antennas to GitHub. Praise be to Guido.beartype 0.1.1 only partially supported PEP 563, @beartype now fully supports all edge cases associated with PEP 563 – including postponed methods, nested functions, closures, and forward references. Forward references merit particular mention. Why? Because of course, forward references are fundamentally indistinguishable from PEP 563-postponed type hints, because PEP 563 was never intended to be usable at runtime. Unsurprisingly, it isn't. While numerous Python packages superficially support PEP 563 by deferring to the broken typing.get_type_hints() function, @beartype is the first and thus far only annotation-based Python package to fully support PEP 563 and thus Python 3.10.@beartype decorator now fully supports the new typing.Literal type hint introduced by Python ≥ 3.9. Note, however, that beartype validators offer similar but significantly more practical support for type hint-based equality comparison in our new beartype.vale.IsEqual class.typing.OrderedDict under Python 3.7.0 and 3.7.1. @beartype now conditionally imports the typing.OrderedDict singleton only if the active Python interpreter targets Python ≥ 3.7.2, the patch release that bizarrely changed the public typing API by introducing this new public attribute. Doing so improves compatibility with both Python 3.7.0 and 3.7.1 and resolves issue #33 – kindly reported by @aiporre, the dancing unicorn that radiates sparkles named Ariel.@beartype now automatically generates test coverage metrics – resolving #20. This includes:
coverage package (if importable under the active Python interpreter). @beartype intentionally leverages the coverage package directly rather than its higher-level pytest-cov wrapper, as the latter offers no tangible benefits over the former while suffering various tangible harms. These include:
tox.ini configuration.-X dev, PYTHONDEVMODE) is now enabled by default under both pytest and tox and thus continuous integration (CI), mildly improving the robustness of our test suite in edge cases that absolutely should never apply (e.g., GIL and memory safety violations) but probably will, because bad things always happen to good coders. It's, like, a law.See Also. The See Also section of our front-facing README.rst
documentation has been significantly expanded with:
(Winsome winners ransom random dendritic endoscopy!)
This release brings explicit support for None, subscripted generics, and PEP 561 compliance after resolving 10 issues and merging 8 pull requests. Cha
Beartype 0.6.0 released.
This release brings explicit support for None, subscripted generics, and PEP 561 compliance after resolving 10 issues and merging 8 pull requests. Changes include:
None singleton. As a return type hint, None is typically used to annotate callables containing no explicit return statement and thus implicitly returning None. @beartype now implicitly reduces None at all nesting levels of type hints to that singleton's type per PEP 484.beartype now fully conforms to PEP 561, resolving issue #25 kindly submitted by best macOS package manager ever @harens. In useful terms, this means that:
beartype now complies with mypy, Python's popular third-party static type checker. If your package had no mypy errors or warnings before adding beartype as a mandatory dependency, your package will still have no mypy errors or warnings after adding beartype as a mandatory dependency.beartype preserves PEP 561 compliance. If your package was PEP 561-compliant before adding beartype as a mandatory dependency, your package will still be PEP 561-compliant after adding beartype as a mandatory dependency. Of course, if your package currently is not PEP 561-compliant, beartype can't help you there. We'd love to, really. It's us. Not you.beartype codebase is now mostly statically rather than dynamically typed, much to our public shame. Thus begins the eternal struggle to preserve duck typing in a world that hates bugs.beartype package now contains a top-level py.typed file, publicly declaring this package to be PEP 561-compliant.beartype developers and automation tooling to trivially install recommended (but technically optional) dependencies. These include:
pip install -e .[dev], installing beartype in editable mode as well as all dependencies required to both locally test beartype and build documentation for beartype from the command line.pip install beartype[doc-rtd], installing beartype as well as all dependencies required to build documentation from the external third-party Read The Docs (RTD) host.README.rst file now documents beartype installation with both Homebrew and MacPorts on macOS, entirely courtesy the third-party Homebrew tap and Portfile maintained by build automation specialist and mild-mannered student @harens. Thanks a London pound, Haren!New beartype.cave types and type tuples, including:
beartype.cave.CallableCTypes, a tuple of all C-based callable types (i.e., types whose instances are callable objects implemented in low-level C rather than high-level Python).beartype.cave.HintGenericSubscriptedType, the C-based type of all subscripted generics if the active Python interpreter targets Python >= 3.9 or beartype.cave.UnavailableType otherwise. This type was previously named beartype.cave.HintPep585Type before we belatedly realized this type broadly applies to numerous categories of PEP-compliant type hints, including PEP 484-compliant subscripted generics.O(n) → O(1) exception handling. @beartype now internally raises human-readable exceptions in the event of type-checking violations with an O(1) rather than O(n) algorithm, significantly reducing time complexity for the edge case of invalid large sequences either passed to or returned from @beartype-decorated callables. For forward compatibility with a future version of beartype enabling users to explicitly switch between constant- and linear-time checking, the prior O(n) exception-handling algorithm has been preserved in a presently disabled form.O(n) → O(1) callable introspection during internal memoization. @beartype now avoids calling the inefficient stdlib inspect module from our private @beartype._util.cache.utilcachecall.callable_cached decorator memoizing functions throughout the beartype codebase. The prior O(n) logic performed by that call has been replaced by equivalent O(1) logic performed by a call to our newly defined beartype._util.func.utilfuncargsubmodule, optimizing function argument introspection without the unnecessary overhead ofinspect`.@beartype now temporarily caches the code object for the currently decorated callable to support efficient introspection of that callable throughout the decoration process. Relatedly, this also has the beneficial side effect of explicitly raising human-readable exceptions from the @beartype decorator on attempting to decorate C-based callables, which @beartype now explicitly does not support, because C-based callables have no code objects and thus no efficient means of introspection. Fortunately, sane code only ever applies @beartype to pure-Python callables anyway. ...right, sane code? Right!?!?beartype.cave.HintPep585Type type, to be officially removed in beartype 0.1.0.str.replace() calls. @beartype now wraps all unsafe internal calls to the low-level str.replace() method with calls to the considerably safer high-level beartype._util.text.utiltextmunge.replace_str_substrs() function, guaranteeing that memoized placeholder strings are properly unmemoized during decoration-time code generation. Thanks to temperate perennial flowering plant @Heliotrop3 for this astute observation and resolution to long-standing background issue #11.KeyPool release validation. @beartype now validates that objects passed to the release() method of the private beartype._util.cache.pool.utilcachepool.KeyPool class have been previously returned from the acquire() method of that class. Thanks to @Heliotrop3, the formidable bug assassin, for their unswerving dedication to the cause of justice with this resolution to issue #13.@beartype now internally provides a highly microoptimized Least Recently Used (LRU) cache for subsequent use throughout the codebase, particularly with respect to caching iterators over dictionaries, sets, and other non-sequence containers. This resolves issue #17, again graciously submitted by open-source bug mercenary @Heliotrop3.@beartype now internally provides a private beartype._util.func.utilfuncorigin.get_callable_origin_label getter synthesizing human-readable labels for the files declaring arbitrary callables, a contribution by master code-mangler @Heliotrop3 resolving issue #18. Thanks again for all the insidious improvements, Tyler! You are the master of everyone's code domain.create-release GitHub Action to @ncipollo's actively maintained release-action, resolving issue #22 kindly submitted by human-AI-hybrid @Heliotrop3.tox-gh-actions GitHub Action streamlining tox usage with our own ad-hoc build matrix that appears to be simpler and faster despite offering basically identical functionality.beartype codebase for unresolved FIXME: comments.tox and GitHub Actions-based continuous integration (CI) configurations now both correctly exercise themselves against both PyPy 3.6 and 3.7, resolving the upstream actions/setup-python#171 issue for beartype.mypy package is installed under CPython. This test is sufficiently critical that we perform it under our CI workflow, guaranteeing test failures on any push or PR violating mypy expectations.README.rst functional test, optionally exercising the syntactic validity of our front-facing README.rst documentation when the third-party docutils package (i.e., the reference reST parser) is installed. This test is sufficiently expensive that we currently avoid performing it under our CI workflow.beartype._util.text.utiltextmunge submodule with lavish attention to regex-based fuzzy testing of the critical number_lines() function. Humble git log shout outs go out to @Heliotrop3 for this mythic changeset that warps the fragile fabric of the GitHub cloud to its own pellucid yet paradoxically impenetrable intentions, resolving issue #24.beartype repository now defines a largely unpopulated skeleton for Sphinx-generated documentation formatted as reST and typically converted to HTML to be hosted at Read The Docs (RTD), generously contributed by @felix-hilden, Finnish computer vision expert and our first contributor! This skeleton enables:
autodoc, viewcode).sphinx_rtd_theme, a third-party Sphinx extension providing RTD's official Sphinx HTML.README.rst documentation signifying the success of the most recent attempt to build and host this skeleton at RTD.sphinx script, building Sphinx-based package documentation when manually run from the command line by interactive developers.beartype mascot "Mr. Nectar Palm" – again courtesy @felix-hilden, because sleep is for the weak and Felix has never known the word.beartype and existing type checkers, resolving clarity concerns raised by @kevinjacobs-progenity at issue #7. Thanks for the invaluable commentary, Kevin!beartype in a live manner.beartype.cave.CallableCTypes.beartype.cave.HintGenericSubscriptedType.beartype.cave.HintPep585Type.(Exogenous exhaustion!)
This stable release significantly improves our front-facing README.rst as well as resolving an unrelated suite of minor issues.
Beartype 0.5.1 released.
This stable release significantly improves our front-facing README.rst as well as resolving an unrelated suite of minor issues.
Specific changes include:
@beartype under real-world use cases.@beartype.@beartype now correctly ignores calls to typing.NewType passed ignorable type hints rather than merely object.@beartype now reports all builtin types (e.g., int) to not be PEP 544-compliant protocols, despite a proper subset of builtin types erroneously claiming to be PEP 544-compliant protocols for strange and probably spurious reasons.--skip-missing-interpreters=false CLI option to force CI failures when one or more Python environments are unavailable, resolving the upstream tox-dev/tox#903 issue for @beartype.(Fastidious fast tracks blast perfidious racks!)
PEP 484-compliant type hints deprecated by PEP 585. @beartype now emits non-fatal warnings of class beartype.roar.BeartypeDecorHintPepDeprecatedWarnin…
Beartype 0.5.0 released.
This stable release implements full compliance for PEP 585 -- Type Hinting Generics in Standard Collections, where "full compliance" means "@beartype deeply type-checks all categories of type hints deeply type-checked by prior stable releases and shallowly type-checks the remainder." See our compliance list and feature matrix for deeply exhausting, enervating, and deenergizing details.
Specific changes include:
@beartype is now fully compliant with PEP 585 -- Type Hinting Generics in Standard Collections. Since that PEP supercedes and largely obsoletes the overwhelming majority of PEP 484, compliance with PEP 585 is critical for all static and runtime type checkers to guarantee forward compatibility with Python's type-checking ecosystem. Note this compliance includes:
@beartype versions, including:
list.tuple.collections.abc.ByteString.collections.abc.MutableSequence.collections.abc.Sequence.class ListOfInts(list[int]): pass), which were implemented in a completely non-orthogonal manner to both PEP 484-compliant generics and PEP 544-compliant protocols and thus required "extreme" handling for PEP 585-specific edge cases.@beartype now supports PEP-compliant type hints not hashable by the hash() builtin and thus impermissible for use as dictionary keys, set members, and memoized callable parameters. While all PEP 484-compliant type hints are hashable, many such hints (e.g., typing.Callable[[], str]) are unhashable when converted into equivalent PEP 585-compliant type hints (e.g., collections.abc.Callable[[], str]), necessitating that @beartype now permissively accept both hashable and unhashable type hints. The disadvantage of the latter is that the private @beartype._util.cache.utilcachecall.callable_cached decorator underlying internal decoration-time memoization performed by @beartype cannot, by definition, memoize calls passed one or more unhashable objects. Ergo, callables accepting one or more unhashable PEP-compliant type hints incur a performance penalty versus hashable PEP-compliant type hints. Note this penalty is only paid at decoration rather than call time and should thus be entirely negligible. Unhashable PEP-compliant type hints include:
typing.Literal[[]], a literal empty list).typing.Annotated[typing.Any, []], the typing.Any singleton annotated by an empty list).@typing.no_type_check. @beartype now supports the PEP 484-compliant @typing.no_type_check decorator by silently ignoring (and thus reducing to a noop for) all callables decorated by that decorator.typing.TYPE_CHECKING. @beartype now supports the PEP 484-compliant typing.TYPE_CHECKING boolean constant by silently reducing to the identity decorator when this boolean is True (i.e., during external static type checking).@beartype (e.g., tuple unions) are now internally coerced into equivalent PEP-compliant type hints, PEP-noncompliant type hint exception classes are now obsolete and have been summarily removed. These include:
beartype.roar.BeartypeCallHintNonPepException.beartype.roar.BeartypeCallHintNonPepParamException.beartype.roar.BeartypeCallHintNonPepReturnException.@beartype now emits non-fatal warnings of class beartype.roar.BeartypeDecorHintPepDeprecatedWarning under Python >= 3.9 for each PEP 484-compliant type hint deprecated by PEP 585 annotating each decorated callables. Critically, note that this deprecates most PEP 484-compliant type hints accepted without warning by prior stable releases and, indeed, most of the existing typing stdlib module. Affected type hints include subscriptions of any of the following objects:
typing.AbstractSet.typing.AsyncGenerator.typing.AsyncIterable.typing.AsyncIterator.typing.Awaitable.typing.ByteString.typing.Callable.typing.ChainMap.typing.Collection.typing.Container.typing.ContextManager.typing.Coroutine.typing.Counter.typing.DefaultDict.typing.Deque.typing.Dict.typing.FrozenSet.typing.Generator.typing.ItemsView.typing.Iterable.typing.Iterator.typing.KeysView.typing.List.typing.MappingView.typing.Mapping.typing.Match.typing.MutableMapping.typing.MutableSequence.typing.MutableSet.typing.Pattern.typing.Reversible.typing.Sequence.typing.Set.typing.Tuple.typing.Type.typing.ValuesView.@beartype now efficiently registers type hints that are standard classes not explicitly compliant with any existing PEP at decoration time and accesses these hints at call time via the standard beartypistry singleton used to resolve all other type hints rather than inefficiently accessing these hints at call time via dictionary lookup into the __annotations__ dunder attribute of the decorated callable, eliminating one dictionary lookup for each such hint for each call to each callable annotated by one or more such hints.@beartype now silently coerces all tuple unions into the equivalent PEP 484 unions at decoration time. Doing so resolves all outstanding performance and usability issues with tuple unions, including:
__annotations__ dunder attribute of the decorated callable, eliminating one dictionary lookup for each such hint for each call to each callable annotated by one or more such hints.(bool, str, bool) is now type-checked as (bool, str)).('muh.TypeName', str)) are now efficiently resolved at call time via the standard beartypistry singleton used to resolve all other forward references rather than inefficiently resolved at call time via non-standard iteration hard-coded into all wrapper functions generated by @beartype for callables annotated by these tuple unions.@beartype._util.cache.utilcachecall.callable_cached decorator are now efficiently called with positional arguments rather than inefficiently called with equivalent keyword arguments. Memoizing keyword arguments is substantially more space- and time-intensive than memoizing the equivalent positional arguments, partially defeating the purpose of memoization in the first place. To enforce this, these internal callables now emit new non-fatal private beartype.roar._BeartypeUtilCallableCachedKwargsWarning warnings when passed one or more keyword arguments.test_api_cave_lib_numpy() unit test exercising NumPy integration in the beartype.cave submodule.test_api_cave_lib_setuptools() unit test exercising setuptools integration in the beartype.cave submodule.beartype.roar submodule:
BeartypeDecorHintPepDeprecatedWarning.BeartypeCallHintNonPepException.BeartypeCallHintNonPepParamException.BeartypeCallHintNonPepReturnException.(Styled turnstiles burn mile-high piles of corrugated corruption!)
This stable release significantly improves beartype's compliance with Python Enhancement Proposals (PEPs), including full compliance with:
Beartype 0.4.0 released.
This stable release significantly improves beartype's compliance with Python Enhancement Proposals (PEPs), including full compliance with:
With respect to PEPs 483 and 484, "full compliance" means "beartype deeply type-checks a lot and at least shallowly type-checks the remainder." With respect to the remaining PEPs, "full compliance" means "beartype deeply type-checks everything."
Specific changes include:
beartype.roar.BeartypeCallCheck*Exception to beartype.roar.BeartypeCallHint*Exception (e.g., from beartype.roar.BeartypeCallCheckPepParamException to beartype.roar.BeartypeCallHintPepParamException).typing.Annotated type-checking. Under Python ≥ 3.9, the @beartype decorator now deeply type-checks parameters and return values annotated by PEP 593 (i.e., "Flexible function and variable annotations")-compliant typing.Annotated type hints in guaranteed constant time.typing.Generic type-checking. The @beartype decorator now type-checks both the shallow types and deep contents of parameters and return values annotated by PEP 484-compliant generics (i.e., type hints subclassing a combination of one or more of the typing.Generic superclass and/or other typing non-class pseudo-superclasses) in guaranteed constant time. Specifically, beartype iteratively walks up the superclass hierarchy of each generic and deeply type-checks that the current parameter or return value satisfies all constraints implied by that superclass. This includes:
typing.IO. This was no small feat, as this abstract base class (ABC) fails to leverage structural subtyping (e.g., via the PEP 544-compliant typing.Protocol ABC) and is thus unusable at runtime... like most typing objects, sadly. Our solution is to ignore the existing implementation of these classes, declare our own internal typing.Protocol-based variants of these classes, and implicitly substitute all instances of these classes with our own variants during our breadth-first traversal (BFS) over PEP-compliant type hints.typing.BinaryIO.typing.TextIO.typing.NewType support. The @beartype decorator now deeply type-checks parameters and return values annotated by PEP 484-compliant new types (i.e., closures generated by the typing.NewType closure factory) in guaranteed constant time.typing.NoReturn support. The @beartype decorator now fully type-checks that callables annotated by the typing.NoReturn singleton return no values (i.e., either halt the active Python process or raise an exception) in guaranteed constant time.typing.Protocol type-checking. Under Python ≥ 3.8, the @beartype decorator now type-checks both the shallow types and deep contents of parameters and return values annotated by PEP 544 (i.e., "Protocols: Structural subtyping (static duck typing)")-compliant protocols (i.e., type hints subclassing a combination of one or more of the typing.Protocol superclass and/or other typing non-class pseudo-superclasses) in guaranteed constant time, similarly to how beartype type-checks generics. This includes:
typing.SupportsAbs.typing.SupportsBytes.typing.SupportsComplex.typing.SupportsIndex.typing.SupportsInt.typing.SupportsFloat.typing.SupportsRound.typing.Tuple type-checking. The @beartype decorator now type-checks both the shallow types and deep contents of parameters and return values annotated by PEP 484-compliant typing.Tuple type hints in guaranteed constant time. The three syntactic variants standardized by PEP 484 are all supported, including:
typing.Tuple[()], type-checking empty tuples.typing.Tuple[{typename}, ...], type-checking tuples containing arbitrarily many items all satisfying the PEP-compliant child hint {typename} (e.g., typing.Tuple[str, ...], a tuple of strings).typing.Tuple[{typename1}, ..., {typenameN}], type-checking tuples containing exactly N items each satisfying a unique PEP-compliant child hint {typenameI} (e.g., typing.Tuple[str, int, float], a tuple containing exactly one string, one integer, and one floating-point number in that order).typing.AsyncContextManager, typing.ContextManager, typing.Match, and typing.Pattern support. The @beartype decorator now type-checks the shallow types (but not deep contents) of parameters and return values annotated by the PEP 484-compliant typing.AsyncContextManager, typing.ContextManager, typing.Match, and typing.Pattern objects. This was no small feat, as the implementations of:
typing.AsyncContextManager and typing.ContextManager define __repr__() dunder methods returning erroneous machine-readable representations obstructing proper support under prior releases.typing.Match and typing.Pattern under Python 3.6 are non-trivially obtuse and basically broken at runtime.@beartype decorator now shallowly type-checks all otherwise supported PEP-compliant type hints parametrized by one or more type variables (e.g., List[T], where T = TypeVar('T')). Previously, this decorator raised exceptions when decorating callables with such hints. This decorator does not yet deeply type-check type variables to be constrained across callable parameters and return values or class methods. For def muh_func(muh_param: List[T]) -> T: return muh_param[0], as example, this decorator now shallowly type-checks the passed parameter muh_param to be a list but does not yet deeply type-check that this function returns values of the same types as items of this list.@beartype decorator now generates optimized code when deeply type-checking items contained in arbitrarily nested PEP-compliant type hints (e.g., typing.List[typing.Union[bool, str, typing.List[int]]]) with PEP 572-style assignment expressions, generalizing similar sequence-specific optimizations introduced with beartype 0.3.0.@beartype decorator now internally catalogues PEP-compliant type hints during the breadth-first search (BFS) it performs over these hints as simple tuples rather than fixed lists, as the former are both mildly faster than the latter and significantly more maintainable (which is the main gain here).@beartype decorator now properly memoizes the type-checking code it generates for PEP-compliant type hints. Prior beartype releases silently failed to memoize this code across different callables annotated by the same hints.beartype.cave submodule, added:
beartype.cave.HintPep585Type attribute, defined as the C-based type of all PEP 585-compliant type hints (i.e., C-based type hint instantiated by subscripting either a concrete builtin container class like list or tuple or an abstract base class (ABC) declared by the collections.abc submodule like collections.abc.Iterable or collections.abc.Sequence) if the active Python interpreter targets at least Python 3.9.0 or beartype.cave.UnavailableType otherwise. This is a prerequisite for PEP 585 support in a subsequent stable release.beartype.roar submodule, renamed:
BeartypeCallCheckException to BeartypeCallHintException.BeartypeCallCheckPepException to BeartypeCallHintPepException.BeartypeCallCheckPepParamException to
BeartypeCallHintPepParamException.BeartypeCallCheckPepReturnException to
BeartypeCallHintPepReturnException.BeartypeCallCheckNonPepException to
BeartypeCallHintNonPepException.BeartypeCallCheckNonPepParamException to
BeartypeCallHintNonPepParamException.BeartypeCallCheckNonPepReturnException to
BeartypeCallHintNonPepReturnException.Beartype 0.3.2 released. Changes include:
Beartype 0.3.2 released. Changes include:
@beartype decorator now officially supports the first stable release of the Python 3.9.x series (i.e., Python 3.9.0).typing.Union[bool, str, typing.Any]) are now ignored by silently reducing to type-checking noops.(Lurid absurdities abstain from a tizzy-fitting kitten's kitsch ditties!)
Beartype 0.3.1 released. Changes include:
Beartype 0.3.1 released. Changes include:
@beartype decorations
of different callables annotated by the same PEP-compliant type hints.
To resolve this, all metadata (notably, the
is_func_wrapper_needs_random_int boolean previously not memoized)
required to communicate state between lower-level memoized callables
and higher-level callables calling the former is now properly passed
up the call stack as return values and thus properly memoized.test_p484(), an existing unit test augmented to exercise this edge
case in the general case, reducing the likelihood of related issues.test_p484_sequence_standard_cached(), a newly defined unit test
exercising this exact edge case, reducing the likelihood of
regressions.(Sandblasted anthers of the crassly amped amphitheater!)
None. You don't deprecate what ain't broke.
Changes include:
@beartype.beartype decorator
now type-checks both the shallow types and deep contents of
parameters and return values annotated with typing-based sequences
in guaranteed constant time, exploiting a variant of the well-known
coupon collector's problem by randomly type-checking one item at each
nesting level of each sequence on each call of each decorated
callable. Currently supported typing-based sequences include:
typing.List.typing.MutableSequence.typing.Sequence.@beartype.beartype decorator now
raise human-readable exceptions exhibiting the exact cause(s) of those
violations, including violations deeply nested in subcontainers (e.g.,
"@beartyped pep_hinted() parameter pep_hinted_param=[([37],)]
violates PEP type hint
typing.List[typing.Sequence[typing.MutableSequence[str]]], as list
item 0 tuple item 0 list item 0 value "37" not str.") Currently
supported typing type hints that now generate human-readable
exception messages include:
typing.List.typing.MutableSequence.typing.Optional.typing.Sequence.typing.Union.@beartype.beartype decorator generates optimal code deeply
type-checking items contained in nested subsequences (e.g.,
List[List[List[str]]]) with PEP 572-style assignment expressions --
the controversial syntactic change that would prompt Guido to
voluntarily abdicate his ancestral leadership role as BDFL. Thanks to
beartype 0.3.0, assignment expressions have finally proved their
real-world utility. This optimization has been quantitatively profiled
to generate type-checking code at least twice as fast as the
equivalent code generated under Python < 3.8 in the case of
triply-nested sequences. Since this performance gap only increases as
the nesting level increases, a doubling of performance is only the
minimum improvement observable under Python >= 3.8. Sequences more
than triply-nested can expect a comparably dramatic speedup.@beartype.beartype decorator is now only conditionally defined in
the bodies of wrapper functions wrapping user-defined callables
accepting one or more positional arguments.beartype._decor.main submodule.typing sequences
and typing unions have been added.(Spherical orthodontics adhere to a glib gibberings of orthopedic ornithopters!)
Nothing published for this version
Nothing published for this version
Nothing published for this version
Your coding agent can read these notes before it upgrades. Set up the MCP server →