Tip
Feed the bear! The bear is rooting around in your refuse pile. You feel sadness.
Beartype Import Hooks¶
Beartype import hooks enforce type hints across your entire app in two lines of code with no runtime overhead. This is beartype import hooks in ten seconds. dyslexia notwithstanding
# Add *ONE* of the following semantically equivalent two-liners to the very
# top of your "{your_package}.__init__" submodule. Start with *THE FAST WAY*.
# ....................{ THE FAST WAY }....................
from beartype.claw import beartype_this_package # <-- this is boring, but...
beartype_this_package() # <-- the fast way
# ....................{ THE LESS FAST WAY }....................
from beartype.claw import beartype_package # <-- still boring, but...
beartype_package('{your_package}') # <-- the less fast way
# ....................{ THE MORE SLOW WAY }....................
from beartype.claw import beartype_packages # <-- boring intensifies
beartype_packages(('{your_package}',)) # <-- the more slow way
Beartype import hooks extend the surprisingly sharp claws of beartype
to
your full app stack, whether anyone else wanted you to do that or not. Claw your
way to the top of the bug heap; then sit on that heap with a smug expression. Do
it for the new guy sobbing quietly in his cubicle.
Import Hooks Overview¶
Beartype import hooks implicitly perform both:
Standard runtime type-checking (ala the
beartype.beartype()
decorator).Standard static type-checking (ala mypy and pyright) but at runtime – and that ain’t standard.
Automate the beartype.beartype()
decorator away today with magical import
hooks published by the beartype.claw
subpackage. When you install import
hooks from beartype, you augment beartype from a pure-runtime
second-generation type-checker into a hybrid runtime-static
third-generation type-checker. 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!
May Neo-Tokyo have mercy on your codebase’s soul.
Import Hooks Overview, Part Deux¶
Beartype import hooks is a hobbit hole so deep we had to deescalate it with decrepit manga panels from Akira. Prepare to enter that hole.
What Is beartype_this_package()?¶
Let’s begin by outlining exactly what beartype_this_package()
does.
As the simplest and most convenient of several import hooks published by the
beartype.claw
subpackage, beartype_this_package()
type-checks
all subsequently imported submodules of {your_package}
. Notably,
beartype_this_package()
:
Implicitly decorates all callables and classes across
{your_package}
by thebeartype.beartype()
decorator. Rejoice, fellow mammals! You no longer need to explicitly decorate anything bybeartype.beartype()
ever again. Of course, you 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.Implicitly appends every PEP 526-compliant annotated variable assignment (e.g.,
muh_int: int = 'Pretty sure this isn't an integer, but not sure.'
) across{your_package}
by a new statement at the same indentation level calling thebeartype.door.die_if_unbearable()
function passed both that variable and that type hint. Never do that manually. Now, you never do.
Examples or we’re lying again. beartype_this_package()
transforms your
{your_package}.{buggy_submodule}
from this quietly broken code that you
insist you never knew about, you swear:
# This is "{your_package}.{buggy_submodule}". It is bad, but you never knew.
import typing as t
bad_global: int = 'My eyes! The goggles do nothing.' # <-- no exception
def bad_function() -> str:
return b"I could've been somebody, instead of a bum byte string."
bad_function() # <-- no exception
class BadClass(object):
def bad_method(self) -> t.NoReturn:
return 'Nobody puts BadClass in the corner.'
BadClass().bad_method() # <-- no exception
…into this loudly broken code that even your unionized QA team can no longer ignore:
# This is "{your_package}.{buggy_submodule}" on beartype_this_package().
# Any questions? Actually, that was rhetorical. No questions, please.
from beartype import beartype
from beartype.door import die_if_unbearable
import typing as t
bad_global: int = 'My eyes! The goggles do nothing.'
die_if_unbearable(bad_global, int) # <-- raises exception
@beartype
def bad_function() -> str:
return b"I could've been somebody, instead of a bum byte string."
bad_function() # <-- raises exception
@beartype
class BadClass(object):
def bad_method(self) -> t.NoReturn:
return 'Nobody puts BadClass in the corner.'
BadClass().bad_method() # <-- raises exception
By doing nothing, you saved five lines of extraneous boilerplate you no longer need to maintain, preserved DRY (Don’t Repeat Yourself), and mended your coworker’s career, who you would have blamed for all this. You had nothing to do with that code. It’s a nothingburger!
Beartype believes you. This is why we beartype_this_package()
.
This is what happens when we don’t beartype_this_package().
Why Is beartype_this_package()?¶
Let’s continue by justifying why you want to use
beartype_this_package()
. Don’t worry. The “why?” is easier than the
“what?”. It often is. The answer is: “Safety is my middle name.”
<– more lies
beartype_this_package()
isolates its bug-hunting action to the current
package. This is what everyone wants to try first. Type-checking only your
first-party package under your control is the safest course of action, because
you rigorously stress-tested your package with beartype. You did, didn’t you?
You’re not making us look bad here? Don’t make us look bad. We already have
GitHub and Reddit for that.
Other beartype import hooks – like beartype_packages()
or
beartyping()
– can be (mis)used to dangerously type-check other
third-party packages outside your control that have probably never been
stress-tested with beartype. Those packages could raise type-checking violations
at runtime that you have no control over. If they don’t now, they could later.
Forward compatibility is out the window. git blame
has things to say about
that.
If beartype_this_package()
fails, there is no hope for your package. Even
though it might be beartype’s fault, beartype will still blame you for its
mistakes.
Import Hooks API¶
Beartype import hooks come in two flavours:
Global import hooks, whose effects encompass all subsequently imported packages and modules matching various patterns.
Local import hooks, whose effects are isolated to only specific packages and modules imported inside specific blocks of code. Any subsequently imported packages and modules remain unaffected.
Global Import Hooks¶
Global beartype import hooks are… well, global. Their claws extend to a horizontal slice of your full stack. These hooks globally type-check all annotated callables, classes, and variable assignments in all subsequently imported packages and modules matching various patterns.
With great globality comes great responsibility.
- beartype.claw.beartype_this_package(*, conf: beartype.BeartypeConf = beartype.BeartypeConf()) None [source]¶
- Parameters:
conf (beartype.BeartypeConf) – Beartype configuration. Defaults to the default configuration performing \(O(1)\) type-checking.
- Raises:
beartype.roar.BeartypeClawHookException –
If either:
This function is not called from a module (i.e., this function is called directly from within a read–eval–print loop (REPL)).
conf
is not a beartype configuration.
Self-package runtime-static type-checking import hook. This hook accepts no package or module names, instead type-checking all annotated callables, classes, and variable assignments across all submodules of the current package (i.e., the caller-defined package directly calling this function).
This hook only applies to subsequent imports performed after this hook, as the term “import hook” implies; previously imported submodules and subpackages remain unaffected.
This hook is typically called as the first statement in the
__init__
submodule of whichever (sub)package you would like to type-check. If you call this hook from:Your top-level
{your_package}.__init__
submodule, this hook type-checks your entire package. This includes all submodules and subpackages across your entire package.Some mid-level
{your_package}.{your_subpackage}.__init__
submodule, this hook type-checks only that subpackage. This includes only submodules and subsubpackages of that subpackage. All other submodules and subpackages of your package remain unaffected (i.e., will not be type-checked).
# At the top of your "{your_package}.__init__" submodule: from beartype import BeartypeConf # <-- boilerplate from beartype.claw import beartype_this_package # <-- boilerplate: the revenge beartype_this_package(conf=BeartypeConf(is_color=False)) # <-- no color is best color
This hook is effectively syntactic sugar for the following idiomatic one-liners that are so cumbersome, fragile, and unreadable that no one should even be reading this:
beartype_this_package() # <-- this... beartype_package(__name__.rpartition('.')[0]) # <-- ...is equivalent to this... beartype_packages((__name__.rpartition('.')[0],)) # <-- ...is equivalent to this.
When in doubt, have no doubt. Just call
beartype_this_package()
.New in version 0.15.0.
beartype_this_package(): It do be like that.
- beartype.claw.beartype_package(package_name: str, *, conf: beartype.BeartypeConf = beartype.BeartypeConf()) None [source]¶
- Parameters:
package_name (str) – Absolute name of the package or module to be type-checked.
conf (beartype.BeartypeConf) – Beartype configuration. Defaults to the default configuration performing \(O(1)\) type-checking.
- Raises:
beartype.roar.BeartypeClawHookException –
If either:
conf
is not a beartype configuration.package_name
is either:Not a string.
The empty string.
A non-empty string that is not a valid package or module name (i.e.,
"."
-delimited concatenation of valid Python identifiers).
Uni-package runtime-static type-checking import hook. This hook accepts only a single package or single module name, type-checking all annotated callables, classes, and variable assignments across either:
If the passed name is that of a (sub)package, all submodules of that (sub)package.
If the passed name is that of a (sub)module, only that (sub)module.
This hook should be called before that package or module is imported; when erroneously called after that package or module is imported, this hook silently reduces to a noop (i.e., does nothing regardless of how many times you squint at it suspiciously).
This hook is typically called as the first statement in the
__init__
submodule of your top-level{your_package}.__init__
submodule.# At the top of your "{your_package}.__init__" submodule: from beartype import BeartypeConf # <-- <Ctrl-c> <Ctrl-v> from beartype.claw import beartype_package # <-- <Ctrl-c> <Ctrl-v> x 2 beartype_package('your_package', conf=BeartypeConf(is_debug=True)) # ^-- 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 want to type-check just one subpackage or submodule of your package rather than your entire package? In that case,beartype_this_package()
is overbearing. badum ching Enterbeartype_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('good_package.m.A.A.d_submodule') # <-- fine-grained precision strike
beartype_package()
shows it true worth, however, in type-checking other people’s code. Because thebeartype.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:# Whenever you want to break something over your knee, never leave your # favorite IDE [read: Vim] without beartype_package(). beartype_package('somebody_elses_package') # <-- blow it up like you just don't care
This hook is effectively syntactic sugar for passing the
beartype_packages()
function a 1-tuple containing only this package or module name.beartype_package('your_package') # <-- this... beartype_packages(('your_package',)) # <-- ...is equivalent to this.
Pretend you didn’t see that. Just call
beartype_package()
.New in version 0.15.0.
Truer words were never spoken, wizened psychic baby lady.
- beartype.claw.beartype_packages(package_names: collections.abc.Iterable[str], *, conf: beartype.BeartypeConf = beartype.BeartypeConf()) None [source]¶
- Parameters:
package_name (collections.abc.Iterable[str]) – Iterable of the absolute names of one or more packages or modules to be type-checked.
conf (beartype.BeartypeConf) – Beartype configuration. Defaults to the default configuration performing \(O(1)\) type-checking.
- Raises:
beartype.roar.BeartypeClawHookException –
If either:
conf
is not a beartype configuration.package_names
is either:Not an iterable.
The empty iterable.
A non-empty iterable containing at least one item that is either:
Not a string.
The empty string.
A non-empty string that is not a valid package or module name (i.e.,
"."
-delimited concatenation of valid Python identifiers).
Multi-package runtime-static type-checking import hook. This hook accepts one or more package and module names in any arbitrary order (i.e., order is insignificant), type-checking all annotated callables, classes, and variable assignments across:
For each passed name that is a (sub)package, all submodules of that (sub)package.
For each passed name that is a (sub)module, only that (sub)module.
This hook should be called before those packages and modules are imported; when erroneously called after those packages and modules are imported, this hook silently reduces to a noop. Squinting still does nothing.
This hook is typically called as the first statement in the
__init__
submodule of your top-level{your_package}.__init__
submodule.# At the top of your "{your_package}.__init__" submodule: from beartype import BeartypeConf # <-- copy-pasta from beartype.claw import beartype_packages # <-- copy-pasta intensifies 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* ), conf=BeartypeConf(is_pep484_tower=True)) # <-- so. u 2 h8 precision.
This hook is the penultimate force in global import hooks. The terser
beartype_this_package()
andbeartype_package()
hooks are effectively syntactic sugar for this verboser hook.One hook to QA them all, and in the darkness of your codebase bind them.
New in version 0.15.0.
It’s almost as if we know what “penultimate” means.
- beartype.claw.beartype_all(*, conf: beartype.BeartypeConf = beartype.BeartypeConf()) None [source]¶
- Parameters:
conf (beartype.BeartypeConf) – Beartype configuration. Defaults to the default configuration performing \(O(1)\) type-checking.
- Raises:
beartype.roar.BeartypeClawHookException – If
conf
is not a beartype configuration.
All-packages runtime-static type-checking import hook. This hook accepts no package or module names, instead type-checking all callables, classes, and variable assignments across all submodules of all packages.
This hook should be called before those packages and modules are imported; when erroneously called after those packages and modules are imported, this hook silently reduces to a noop. Not even squinting can help you now.
This hook is typically called as the first statement in the
__init__
submodule of your top-level{your_package}.__init__
submodule.# At the top of your "{your_package}.__init__" submodule, from beartype import BeartypeConf # <-- @beartype seemed so innocent, once from beartype.claw import beartype_all # <-- where did it all go wrong? beartype_all(conf=BeartypeConf(claw_is_pep526=False)) # <-- U WILL BE ASSIMILATE
This hook is the ultimate import hook, spasmodically unleashing a wave of bug-defenestrating action over the entire Python ecosystem. After calling this hook, any package or module authored by anybody (including packages and modules in CPython’s standard library) will be subject to the iron claw of
beartype.claw
. Its rule is law!This hook is the runtime equivalent of a full-blown pure-static type-checker like mypy or pyright, enabling full-stack runtime-static type-checking over your entire app. This includes submodules defined by both:
First-party proprietary packages authored explicitly for this app.
Third-party open-source packages authored and maintained elsewhere.
Nothing is isolated. Everything is permanent. Do not trust this hook.
Caveat Emptor: Empty Promises Not Even a Cat Would Eat¶
This hook imposes type-checking on all downstream packages importing your package, which may not necessarily want, expect, or tolerate type-checking. This hook is not intended to be called from intermediary APIs, libraries, frameworks, or other middleware. Packages imported by other packages should not call this hook. This hook is only intended to be called from full-stack end-user applications as a convenient alternative to manually passing the names of all packages to be type-checked to the more granular
beartype_packages()
hook.This hook is the extreme QA nuclear option. Because this hook is the extreme QA nuclear option, most codebases should not call this hook.
beartype
cannot be held responsible for a sudden rupture in the plenæne of normalcy, the space-time continuum, or your once-stable job. Pour one out for those who are about to vitriolically explode their own code.Nuke Python from orbit. Because now you can.
New in version 0.15.0.
The beartype_all() lifestyle. Short but sweet.
Import Hook Configuration¶
Beartype import hooks accept an optional keyword-only conf
parameter whose
value is a beartype configuration (i.e., beartype.BeartypeConf
instance), defaulting to the default beartype configuration BeartypeConf()
.
Unsurprisingly, that configuration configures the behaviour of its hook: e.g.,
# 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, beartype.BeartypeConf
has been equipped with
import hook-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 annotated variable assignments likemuh_int: int = 'Pretty sure this isn't an integer.'
. Although this is usually what everyone wants, this may not be what someone suspicious wearing aviator goggles, a red velvet cape, and too-tight black leather wants. Nobody knows what those people want. If you are such a person, consider disabling this option to reduce type safety and destroy your code like Neo-Tokyo vs. Mecha-Baby-Godzilla: …who will win!?!?BeartypeConf(warning_cls_on_decorator_exception: Optional[Type[Warning]] = None)
. By default,beartype.claw
emits non-fatal warnings rather than fatal exceptions raised by thebeartype.beartype()
decorator at decoration time. This is usually what everyone wants, becausebeartype.beartype()
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 ofbeartype.claw
against those edge cases,beartype.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 wantbeartype.claw
to raise a fatal exception on the first such edge case in your codebase – perhaps because you want to either see the full exception traceback or punish your coworkers who are violating typing standards by trying to use an imported module as a type hint? …this actually happened In this case, consider:Passing
None
as the value of this parameter. Doing so forcesbeartype.claw
to act strictly, inflexibly, and angrily. Expect spittle-flecked mouth frothing and claws all over the place:
# In your "{your_package}.__init__" submodule, raise exceptions because you # hate worky. The CI pipeline you break over your knee may just be your own. 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*