pypi beartype 0.19.0rc1
Beartype 0.19.0 Release Candidate 1: The Tough Get Committing

latest releases: 0.21.0, 0.21.0rc0, 0.20.2...
12 months ago

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.


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?

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...

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

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

Thanks so much, masters of fintech and metrology.


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

infer_hint(): Because you hate type hints, make @beartype write them all for you

Type 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:


that smiling face is you hammering the commit button despite CI failures from @beartype.

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 Diver

Let @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>


what it feels like to live in Canada, friends.

BeartypeAI™: Even a Broken Algorithm is Right Twice a Commit

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:

  1. Feed beartype.door.infer_hint() arbitrarily complex objects.
  2. Annotate those objects with the type hints it regurgitates.

It's impossible to unpack how much madness is happening inside BeartypeAI™. Let's try anyway.


son of a submariner bounces on the hot sand like bugs across your git log.

Homebrew Collection Classes: Annotate the Unannotatable

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...


this is for @Moosems!

Exhibit (A) – Weirdo Custom List

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. 🤣


a lone dev seeks the calm in the midst of the bug storm, yet finds only a rainbow

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.


@beartype casts Meteor Suplex on the runaway train that is your codebase

Tensors Type Hints: Gods, They Suck Too.

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? 😮‍💨

# 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?


don't be that one guy who falls off the floating island. just... don't

BeartypeAI™: So It Does Structural Similarity Too Now, Huh?

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.


so tired. so very, very tired.

One Ping to Rule Them All and in the GitHub Announcement Summon Them

@pablovela5620, @thiswillbeyourgithub, @WeepingClown13, @JWCS, @Logan-Pageler, @knyazer, @Moosems, @frrad, @minmax, @jaanli, @jonnyhyman, @f-fuchs, @jennydaman, @denballakh, @bionicles

Don't miss a new beartype release

NewReleases is sending notifications on new releases.