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

typing_extensions.Annotated not supported #120

Closed
hodgespodge opened this issue Oct 10, 2023 · 4 comments
Closed

typing_extensions.Annotated not supported #120

hodgespodge opened this issue Oct 10, 2023 · 4 comments

Comments

@hodgespodge
Copy link
Contributor

Howdy,

For the time being, I'm unfortunately stuck targeting python 3.8. As such, I am using the typing_extensions package to get Annotated.
My goal is to create a custom type checker using beartype.vale.Is. The code is confirmed to work with the new python typing, but fails for typing_extensions. I think it would be great to support typing_extensions.Annotated since beartype.beartype recommends using typing_extensions for python < 3.9 in its documentation.

import sys
from beartype.vale import Is
from plum import dispatch

if sys.version_info < (3, 9):
    from typing_extensions import Annotated
else:
    from typing import Annotated


class MyClass():
    def __init__(self) -> None:
        self.special_attr = 'special_value'

class ClassWithAnnotatedTypeChecker():

    my_type_check = Annotated[object, Is[lambda value: hasattr(value,'special_attr')]]

    @dispatch
    def myFunction(self, value: my_type_check):
        print(f'Value has special_attr={value.special_attr}')

    @dispatch
    def myFunction(self, value: str):
        print(f'Value is a string={value}')

    @dispatch
    def myFunction(self, value: int):
        print(f'Value is an int={value}')


my_class = MyClass()
my_class_with_annotated_type_checker = ClassWithAnnotatedTypeChecker()

# This all works for python >= 3.9
# Else: plum.resolver.ambiguousLookupError
my_class_with_annotated_type_checker.myFunction(my_class)
my_class_with_annotated_type_checker.myFunction('Hello')
my_class_with_annotated_type_checker.myFunction(1)

Thanks!

@wesselb
Copy link
Member

wesselb commented Oct 11, 2023

Hey @hodgespodge! :)

I think the problem is as follows:

>>> from typing_extensions import Annotated

>>> from beartype.vale import Is

>>> from beartype.door import TypeHint, is_bearable

>>> is_bearable("hello", Annotated[object, Is[lambda value: hasattr(value, 'special_attr')]])
True

I've opened an issue at @beartype to get the bear master's (@leycec's) thoughts.

leycec added a commit to beartype/beartype that referenced this issue Oct 12, 2023
This commit resolves an edge case with respect to beartype validators in
which @beartype unconditionally (and thus erroneously) ignored *all*
type hints 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 issue #290 kindly
submitted by Plum maestro @wesselb (Wessel) *and* beartype/plum#120
kindly submitted by professional hodge-podger @hodgespodge.
Specifically, this commit:

* Improves our private
  `beartype._util.hint.pep.proposal.utilpep593.is_hint_pep593_ignorable()`
  tester to avoid ignoring PEP 593-compliant type hints annotated by one
  or more beartype validators, even if those same hints annotate an
  ignorable type hint (e.g., the root `object` superclass).
* Exhaustively unit tests this edge case.

(*Ineluctable defection of delectable perfection!*)
@leycec
Copy link
Member

leycec commented Oct 12, 2023

Resolved by beartype/beartype@76e609dd484339ae. I'm willing to pretend this didn't happen if everyone else is. 🥲

@leycec leycec closed this as completed Oct 12, 2023
@wesselb
Copy link
Member

wesselb commented Oct 12, 2023

@hodgespodge if this isn’t top-notch super quick bug solving, then I don’t know what is. Hats off to @leycec!

@hodgespodge
Copy link
Contributor Author

Thanks a ton both of y'all!

leycec added a commit to beartype/beartype that referenced this issue Oct 14, 2023
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):
  1. Detects **class redefinition** (i.e., redefinition of a previously
     `@beartype`-decorated class with the same name in the same module,
     usually but *not* necessarily due to hot reloading).
  1. On the first redefinition of *any* class, clears all **caches**
     (i.e., @beartype-specific internal caches that could possibly
     contain the prior definition of that redefined class).
* **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:

  ```python
  >>> 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.,

  ```python
  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:
  * The fully-qualified names of the modules declaring the classes to
    which they refer.
  * The unqualified basenames of those classes.
* **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.,

  ```python
  @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:
  * A [revised high-level introduction](https://beartype.readthedocs.io)
    to @beartype, which now promotes our principal
    `beartype.claw.beartype_this_package()` import hook. Ideally, this
    is what almost everyone should now be using.
  * A new [`beartype.claw` API
    page](https://beartype.readthedocs.io/en/latest/api_claw). 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!
  * A new ["What does 'pure-Python' mean?" FAQ
    entry](https://beartype.readthedocs.io/en/latest/faq/#what-does-pure-python-mean).
    Reading is fun, even if you learn nothing and squander precious time
    that could have been better spent breathlessly watching Keanu Reeves
    do Keanu Reeves things.
  * A new ["What does 'hybrid runtime-static' mean?" FAQ
    entry](https://beartype.readthedocs.io/en/latest/faq/#what-does-hybrid-runtime-static-mean-pretty-sure-you-made-that-up-too).
    You will be bored to tears even as you gnash your teeth in mute
    frustration, which really cannot be good for your teeth.
  * A new ["What does 'third-generation type-checker' mean?" FAQ
    entry](https://beartype.readthedocs.io/en/latest/faq/#third-generation-type-checker-doesn-t-mean-anything-does-it).
    Fun with Tedious Historical Facts That Will Bore You and Were
    Probably Just Made Up By @leycec Anyway: *The Reckoning.*
  * Injects superfluous images illegally culled from *Akira* into this
    documentation, just because. If @beartype can do something,
    @beartype will do something.

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. (An explosive impulse detonates tons of decidedly pulsating lozenges!*)
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

3 participants