Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature Request] Granular exception messages for synthesized beartype validators #72

Closed
dycw opened this issue Nov 17, 2021 · 8 comments

Comments

@dycw
Copy link

dycw commented Nov 17, 2021

Hi, I have been studying this library and noticed that the README suggests one prefer conjoining ("and"-ing) conditions over enumerating them (","-separating) them.

from dataclasses import dataclass
from typing import Annotated
from beartype import beartype

@dataclass
class Foo:
    x: int
    y: int

@beartype
def conjoined(
    x, y
) -> Annotated[
    "Foo",
    Is[lambda foo: foo.x + foo.y >= 0] # force separation of lines so only 1 lambda per line
    & Is[lambda foo: foo.x + foo.y <= 10],
]:
    return Foo(x, y)

conjoined(100, 100)
BeartypeCallHintPepReturnException: @beartyped conjoined() return "Foo(x=100, y=100)" violates type hint typing.Annotated[ForwardRef('Foo'), Is[lambda foo: foo.x + foo.y >= 0] & Is[lambda foo: foo.x + foo.y <= 10]], as "Foo(x=100, y=100)" violates validator Is[lambda foo: foo.x + foo.y >= 0] & Is[lambda foo: foo.x + foo.y <= 10].

However, this doesn't go in and tell me which condition failed (maybe 1, maybe all).

Enumeration seems to do the trick though:

@beartype
def enumerated(
    x, y
) -> Annotated[
    "Foo",
    Is[lambda foo: foo.x + foo.y >= 0],
    Is[lambda foo: foo.x + foo.y <= 10],
]:
    return Foo(x, y)

enumerated(100, 100)
BeartypeCallHintPepReturnException: @beartyped enumerated() return "Foo(x=100, y=100)" violates type hint typing.Annotated[ForwardRef('Foo'), Is[lambda foo: foo.x + foo.y >= 0], Is[lambda foo: foo.x + foo.y <= 10]], as "Foo(x=100, y=100)" violates validator Is[lambda foo: foo.x + foo.y <= 10].

To be it seems enumerated conditions could / should be preferred. Could you explain any arguments in favour of conjunction?

@leycec
Copy link
Member

leycec commented Nov 18, 2021

Could you explain any arguments in favour of conjunction?

None whatsoever. Just kidding. Almost had you there for a sec, huh? Okay. So:

But... you're absolutely right. beartype should always raise human-readable exceptions. And that's doubly the case for complex high-level validators composed from lower-level validators. beartype currently isn't raising human-readable exceptions for those. That's bad. We'd better fix that before anyone else notices, so I've prioritized this above everything else I'm currently doing here.

Thanks for the detailed heads up on this badness, Derek. You're awesome – and so are all these helpful issues you've been opening up! Two misshapen thumbs way up. 👍 👍

@leycec leycec changed the title Question: enumerated vs conjoined conditions [Feature Request] Granular exception messages for synthesized beartype validators Nov 18, 2021
@leycec
Copy link
Member

leycec commented Nov 18, 2021

To remind myself exactly what I'm doing here, I've taken the liberty of "lightly" editing the issue title.

It's for the good of my future self. Really, that guy wouldn't remember his cat's name if it wasn't soldered straight onto his breadboard. 🧓

@dycw
Copy link
Author

dycw commented Nov 20, 2021

Thanks, merci and ありがとうございます for the bearific, pawesome library!

P.S. Really looking forward to O(n log n) runtime checking.

@leycec
Copy link
Member

leycec commented Nov 20, 2021

Awwwww. You are too nice, Derek. I graciously accept your multilingual merci + ありがとうございます k-k-k-iller combo and humbly boomerang you back a de rien + どういたしまして extended finisher.

Fist-bumping high fives all around! ✋

O(n log n) runtime checking.

...heh. My facial twitch just got worse. Please tell me you meant O(log n). Please. If so, then... yes! We'd love to do both that and O(n) runtime type-checking for you and everyone. All I ask in return is five spare body clones willing to do my absolute bidding without trying to replace me.

Body clones: can't really trust 'em, can't really open-source without 'em.

@dycw
Copy link
Author

dycw commented Nov 20, 2021

Lol good catch. Without my normal office Mon-Fri coffee, I'm not quite as sharp. I can do O(n log n) by myself lol.

leycec added a commit that referenced this issue Dec 7, 2021
This commit is the first in a commit chain improving the granularity
(i.e., specificity) of exception messages raised by high-level beartype
validators synthesized from lower-level beartype validators (e.g., via
overloaded set theoretic operators like `|`, `&`, and `~`), en-route to
resolving issue #72 kindly submitted by the unwreckable type-hinting
guru Derek Wan (@dycw). Specifically, this commit drafts and documents
(but has yet to meaningfully implement) a new
`BeartypeValidator.get_diagnosis()` getter method returning a detailed
pretty-printed diagnosis of how any object either satisfies or fails to
satisfy any validator. Unrelatedly, this commit also optimizes the
runtime efficiency of our core
`beartype._util.kind.utilkinddict.die_if_mappings_two_items_collide()`
validation function and thus the `@beartype` decorator at decoration
time. (*Unsung doom-brung looms caromed off the bottom rung!*)
@leycec
Copy link
Member

leycec commented Dec 7, 2021

It begins. Thanks again for all the apropos issues, Derek. You rock everything. 🪨 ← unsure what that is

leycec added a commit that referenced this issue Dec 8, 2021
This commit is the next in a commit chain improving the granularity
(i.e., specificity) of exception messages raised by high-level beartype
validators synthesized from lower-level beartype validators (e.g., via
overloaded set theoretic operators like `|`, `&`, and `~`), en-route to
resolving issue #72 kindly submitted by the unwreckable type-hinting
guru Derek Wan (@dycw). Specifically, this commit implements, documents,
and tests a new `BeartypeValidator.get_diagnosis()` getter method
returning a detailed pretty-printed diagnosis of how any object either
satisfies or fails to satisfy any validator. This implementation
currently only suffices for lower-level validators *not* encapsulating
one or more other lower-level validators (e.g., via set theoretic
operators). Unrelatedly, this commit finalizes our optimization of the
`beartype._util.kind.utilkinddict.die_if_mappings_two_items_collide()`
validation function and thus the `@beartype` decorator at decoration
time. (*Dramatized materialization of a dromedary's ostracization!*)
leycec added a commit that referenced this issue Dec 9, 2021
This commit is the next in a commit chain improving the granularity
(i.e., specificity) of exception messages raised by high-level beartype
validators synthesized from lower-level beartype validators (e.g., via
overloaded set theoretic operators like `|`, `&`, and `~`), en-route to
resolving issue #72 kindly submitted by the unwreckable type-hinting
guru Derek Wan (@dycw). Specifically, this commit defines a draft
implementation of the new `BeartypeValidator.get_diagnosis()` getter
closure unique to negation beartype validators dynamically synthesized
by the `BeartypeValidator.__invert__()` dunder method. Naturally,
nothing is tested. Why bother testing, right? We know! (*Incurable incunabula!*)
leycec added a commit that referenced this issue Dec 10, 2021
This commit is the next in a commit chain improving the granularity
(i.e., specificity) of exception messages raised by high-level beartype
validators synthesized from lower-level beartype validators (e.g., via
overloaded set theoretic operators like `|`, `&`, and `~`), en-route to
resolving issue #72 kindly submitted by the unwreckable type-hinting
guru Derek Wan (@dycw). Specifically, this commit finalizes our
implementation of the new `BeartypeValidator.get_diagnosis()` getter
closure unique to conjunctions, disjunctions, and negations dynamically
synthesized from explicit beartype validators by the
`BeartypeValidator.__and__()`, `BeartypeValidator.__or__()`, and
`BeartypeValidator.__invert__()` dunder method. Some things are tested.
Most things are not. We march to the beat of our own QA drum here.
(*Luxurious lumens demonstrate remonstratory fluxes!*)
leycec added a commit that referenced this issue Dec 11, 2021
This commit is the last in a commit chain improving the granularity
(i.e., specificity) of exception messages raised by high-level beartype
validators synthesized from lower-level beartype validators (e.g., via
overloaded set theoretic operators like `|`, `&`, and `~`), resolving
issue #72 kindly submitted by the unwreckable type-hinting guru Derek
Wan (@dycw). Specifically, this commit fundamentally refactors our
implementation of conjunctions, disjunctions, and negations dynamically
synthesized from beartype validators to leverage a new internal subclass
hierarchy of beartype validator operators. Doing so sanitizes diagnosis
of validation failures in non-trivial use cases involving multiple
nested conjunctions, disjunctions, and/or negations. (*Spontaneous instantaneousness!*)
@leycec
Copy link
Member

leycec commented Dec 11, 2021

Boom. Catastrophic explosions can be heard ominously rumbling in the distance as dust from the ceiling showers around your fragile laptop monitor. This can only mean one thing...

Either Godzilla and Mothra about to tumble on your front porch or this issue was brutally resolved! Yup. It's actually the latter. beartype 0.10.0 (to be released before 2022, maybe: that's a promise) now appends an exhaustive diagnosis explaining "Wtf, bro?" to type hinting failures resulting from beartype validators: e.g.,

>>> from dataclasses import dataclass
>>> from typing import Annotated
>>> from beartype import beartype
>>> from beartype.vale import Is
>>> @dataclass
... class Foo:
...     x: int
...     y: int
>>> @beartype
... def conjoined(
...     x, y
... ) -> Annotated[
...     "Foo",
...     Is[lambda foo: foo.x + foo.y >= 0] # force separation of lines so only 1 lambda per line
...     & Is[lambda foo: foo.x + foo.y <= 10],
... ]:
...     return Foo(x, y)
>>> conjoined(100, 100)
Traceback (most recent call last):
  File "/home/leycec/tmp/sappy.py", line 22, in <module>
    conjoined(100, 100)
  File "<string>", line 20, in conjoined
  File "/home/leycec/py/beartype/beartype/_decor/_error/errormain.py", line 301, in raise_pep_call_exception
    raise exception_cls(  # type: ignore[misc]
beartype.roar.BeartypeCallHintPepReturnException: @beartyped conjoined() return "Foo(x=100, y=100)" violates type hint typing.Annotated[ForwardRef('Foo'), Is[lambda foo: foo.x + foo.y >= 0] & Is[lambda foo: foo.x + foo.y <= 10]], as "Foo(x=100, y=100)" violates validator Is[lambda foo: foo.x + foo.y >= 0] & Is[lambda foo: foo.x + foo.y <= 10]:
    False == (
     True ==     Is[lambda foo: foo.x + foo.y >= 0] &
    False ==     Is[lambda foo: foo.x + foo.y <= 10]
             ).

Of course, validator diagnoses elegantly scale to deeply nested validators with properly indented pretty-printing. We may all be thankful for that, because deeply nested validators get crazy like Godzilla hopped up on too much spent plutonium rods dumped off the Bay of Tokyo yet again:

>>> from beartype import beartype
>>> from beartype.vale import Is
>>> from typing import Annotated
>>> IsLengthy = Is[lambda text: len(text) > 80]
>>> IsSentence = Is[lambda text: text and text[-1] == '.']
>>> IsQuoted = Is[lambda text: '"' in text or "'" in text]
>>> IsLengthyOrUnquotedSentence = IsLengthy | (IsSentence & ~IsQuoted)
>>> @beartype
... def still_life(serenity_painted_death: Annotated[
...     str, IsLengthyOrUnquotedSentence]) -> str:
...     return serenity_painted_death * 666  # <-- feels good
>>> still_life('Black paragon in lingering breath. "Yeah!" White-faced...')
Traceback (most recent call last):
  File "/home/leycec/tmp/sappy.py", line 16, in <module>
    still_life('Black paragon in lingering breath. "Yeah!" White-faced...')
  File "<string>", line 24, in still_life
  File "/home/leycec/py/beartype/beartype/_decor/_error/errormain.py", line 301, in raise_pep_call_exception
    raise exception_cls(  # type: ignore[misc]
beartype.roar.BeartypeCallHintPepParamException: @beartyped still_life() parameter serenity_painted_death='Black paragon in lingering breath. "Yeah!" White-faced...' violates type hint typing.Annotated[str, Is[lambda text: len(text) > 80] | Is[lambda text: text and text[-1] == '.'] & ~Is[lambda text: '"' in text or "'" in text]], as 'Black paragon in lingering breath. "Yeah!" White-faced...' violates validator Is[lambda text: len(text) > 80] | Is[lambda text: text and text[-1] == '.'] & ~Is[lambda text: '"' in text or "'" in text]:
    False == (
    False ==     Is[lambda text: len(text) > 80] |
    False ==     (
     True ==         Is[lambda text: text and text[-1] == '.'] &
    False ==         ~(
     True ==             Is[lambda text: '"' in text or "'" in text]
                     )
                 )
             ).

The sweat on my brow is real. 😅 💦

don't do it, godzilla!

@leycec leycec closed this as completed Dec 11, 2021
@dycw
Copy link
Author

dycw commented Dec 12, 2021

Quick, someone get @leycec some water — he's on fire!

This is an awesome feature, think beartype 0.10 might be an awesome Christmas present!

leycec added a commit that referenced this issue Feb 9, 2022
This release titillates with scintillating support for **[PEP 557 --
Data Classes][PEP 557]**, **[PEP 570 -- Python Positional-Only
Parameters][PEP 570]**, and **[PEP 604 -- Allow writing union types as
X | Y][PEP 604]**.

This release resolves a bone-crushing **30 issues** (mostly shameless
dupes of one another, admittedly) and merges **3 pull requests.**
World-girdling changes include:

## Compatibility Added

* **[PEP 557 -- Data Classes][PEP 557].** `@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:
  * **Dataclass-specific initialization-only instance variable type
    hints** (i.e., `dataclasses.InitVar[...]`).
  * The implicit `__init__()` method generated by `@dataclass` for
    dataclasses through a clever one-liner employed by @antonagestam
    (Anton Agestam) the ageless Swede that I stan for.
* **[PEP 570 -- Python Positional-Only Parameters][PEP 570].**
  `@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).
* **[PEP 604 -- Allow writing union types as X | Y][PEP 604].**
  `@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!

## Compatibility Improved

* **[PEP 484 -- Type Hints][PEP 484],** including:
  * **`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:
    * Since the `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.
* **Unsubscripted NumPy 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.
* **Mypy ≥ 0.920.** `@beartype` now squelches ignorable mypy complaints
  first introduced by mypy 0.920, including:
  * **Explicit reexport errors.** `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`.
  * **Version-dependent errors.** Previously, mypy permitted imports
    against standard library modules introduced in newer CPython
    versions to be squelched with the usual ``"# 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.

## Features Added

* **Compatibility API.** `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][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:
  * Between `beartype.typing` and `typing` across all Python versions.
  * With mypy when importing from `beartype.typing`.
* **Configuration API** (i.e., public attributes of the `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:
    * An `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.
    * A `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.
  * **Wrapper function debuggability.** Enabling the `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]:`

      ``` python
      (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) ):
      ```
* **Decorator modality.** `@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.,

    ``` python
    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)
    ```
  * Specifically, this commit extricates our core
    `@beartype` decorator into a new private `beartype._decor._core`
    submodule in preparation for subsequently memoizing closures
    encapsulating that decorator returned by invocations of the form
    `@beartype.beartype(conf=BeartypeConf(...))`
* **Declarative instance validator.** `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]]`. ← *bruh*
* **Functional API.** `beartype` 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.

## Features Improved

* **Exception message granularity,** including exceptions raised for:
  * **Disordered builtin decorators.** `@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 validators.** `@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.

## Features Optimized

* **`@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.
 * **Worst-case nested data structures.** `@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:
  * **Code objects.** `@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.
  * **Exception messages.** `@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.
  * **Fixed lists.** `@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>`
  * **Parameter introspection.** `@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.

## Features Deprecated

* **Badly named exception classes,** to be removed in `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`.

## Documentation Revised

* The *Frequently Asked Questions (FAQ)* section of our front-facing
  `README.rst` documentation now sports a medley of new entries,
  including instructions on:
  * **Boto3 integration,** enabling end users to type-check runtime
    types dynamically fabricated by Boto3 (i.e., the official Amazon Web
    Services (AWS) Software Development Kit (SDK) for Python), resolving
    issue #68 kindly submitted by Paul Hutchings (@paulhutchings) – the
    supremely skilled sloth rockin' big shades and ever bigger
    enthusiasm for well-typed Python web apps. Relatedly, the @beartype
    organization now officially hosts [`bearboto3`, Boto3 @beartype
    bindings by (*wait for it*) @paulhutchings](https:
    //github.com/beartype/bearboto3).
  * **Mock type type-checking,** resolving issue #92 kindly submitted by
    @Masoudas (Masoud Aghamohamadian-Sharbaf – wish I had an awesome
    name like that). Gratuitous shoutouts to @TeamSpen210 for the quick
    save with a ludicrous two-liner solving everything.

  [PEP 484]: https://www.python.org/dev/peps/pep-0484
  [PEP 557]: https://www.python.org/dev/peps/pep-0557
  [PEP 570]: https://www.python.org/dev/peps/pep-0570
  [PEP 585]: https://www.python.org/dev/peps/pep-0585
  [PEP 604]: https://www.python.org/dev/peps/pep-0604

The hype train is now boarding. All aboooooooard! (*Classless masterless masterclass!*)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants