Skip to content

Commit

Permalink
**Beartype 0.9.0** released.
Browse files Browse the repository at this point in the history
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 484], [PEP 563][PEP 563], [PEP 585][PEP 585], [PEP 586][PEP
586], [PEP 589][PEP 589], and [PEP 593][PEP 593].

This release resolves **6 issues** and merges **2 pull requests.**
Non-cringe-worthy changes include:

## Compatibility Improved

* **Asynchronous coroutines.** `@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 484], [PEP 585][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).
* **Asynchronous and synchronous generators.** `@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:
  * Asynchronous generators to be annotated with either:
    * A [PEP 484][PEP 484]-compliant `typing.AsyncGenerator[...]` type
      hint.
    * A [PEP 585][PEP 585]-compliant
      `collections.abc.AsyncGenerator[...]` type hint.
  * Synchronous generators to be annotated with either:
    * A [PEP 484][PEP 484]-compliant `typing.Generator[...]` type hint.
    * A [PEP 585][PEP 585]-compliant `collections.abc.Generator[...]`
      type hint.
* **Beartype validators under Python 3.6 and 3.7.** Beartype validators
  (which previously required Python ≥ 3.8) are now portably usable
  across *all* supported Python versions, including Python 3.6 and 3.7.
  `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][PEP 560]-compliant
  `__class_getitem__()` dunder method supported only under Python ≥ 3.8
  and *not* supported by mypy. Naturally, this was pain.
* **Typed NumPy arrays.** `@beartype` now accepts all valid variants of
  `numpy.typing.NDArray[{dtype}]` type hints also accepted by mypy,
  where `{dtype}` is either:
  * An actual NumPy dtype (e.g.,
    `numpy.typing.NDArray[numpy.dtype(numpy.float64)]`).
  * An object safely coercible into an actual NumPy dtype, including:
    * A scalar NumPy type (e.g., `numpy.typing.NDArray[numpy.float64]`).
  * A scalar NumPy abstract base class (ABC) (e.g.,
    `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:
  * If the optional third-party `typing_extensions` package is also
    importable, `@beartype` now deeply type-checks these hints as
    expected.
  * Else, `@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.](https://www.youtube.com/watch?v=zTABEQ4Qh5Y) How could
    this be *our* fault!?!? <sup>it's totally our fault</sup>
* **[PEP 484][PEP 484].** `@beartype` now:
  * Deeply type-checks [PEP 484][PEP 484]-compliant `typing.Type` type
    hints, validating parameters and returns to be subclasses of the
    subscripted type. This includes *all* syntactic variants
    standardized by [PEP 484][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.
  * Partially deeply type-checks [PEP 484][PEP 484]-compliant
    `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).
  * Shallowly type-checks [PEP 484][PEP 484]-compliant **parametrized
    type variables** (i.e., `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:
    * Constraints passed as positional parameters (e.g.,
      `typing.TypeVar('T', str, bytes)`) as a union of those positional
      parameters instead (e.g., as `typing.Union[str, bytes]`).
    * An upper bound passed as the `bound` keyword parameter (e.g.,
      `typing.TypeVar('T', bound=float)`) as that upper bound instead
      (e.g., as `float`).
  * Emits more self-explanatory deprecation warnings for [PEP 484][PEP
    484]-compliant type hints deprecated by [PEP 585][PEP 585] under
    Python ≥ 3.9. Specifically:
    * A [new "PEP 484 Deprecations"
      subsection](https://github.com/beartype/beartype#pep-484-deprecations)
      has been added to our front-facing `README.rst` documentation.
    * Our existing `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.
    * This deprecation warning message now references [this "PEP 484
      Deprecations"
      subsection](https://github.com/beartype/beartype#pep-484-deprecations)
      subsection with improved clarity and an anxiety-provoking speech
      invoking developer horror by suggesting your codebase will die in
      2025 unless you do something painful today.
* **[PEP 563][PEP 563].** `@beartype` now:
  * Reverts our unconditionally enable of [PEP 563][PEP 563] under
    Python ≥ 3.10 in solidarity with the recent (pydantic|FastAPI)-led
    casus belli. [PEP 563][PEP 563] must now be explicitly enabled under
    *all* Python interpreters via the standard `from __future__ import
    annotation` pragma.
  * Explicitly avoids resolving [PEP 563][PEP 563]-postponed type hints
    that are relative forward references to parent callables or classes;
    by definition, parent callables and classes have yet to be defined
    and thus *cannot* be reasonably resolved and thus *must* be
    preserved as relative forward references until eventually resolved
    at call time.
* **[PEP 585][PEP 585].** `@beartype` now:
  * Deeply type-checks supports [PEP 585][PEP 585]-compliant `type` type
    hints, exactly as described for [PEP 484][PEP 484]-compliant
    `typing.Type` type hints above.
  * Partially deeply type-checks [PEP 585][PEP 585]-compliant
    `collections.abc.Coroutine` type hints, exactly as described for
    [PEP 484][PEP 484]-compliant `typing.Coroutine` type hints above.
  * **Deduplicates all [PEP 585][PEP 585] type hints,** dramatically
    reducing both the space and time complexity associated with such
    hints. [PEP 484][PEP 484]-compliant type hints (e.g., `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 484], [PEP 585][PEP 585] fails to deduplicate its hints
    (e.g., `list[str] is not list[str]`). `@beartype` now internally
    deduplicates duplicated [PEP 585][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.
* **[PEP 586][PEP 586]** (i.e., `Literal`). `@beartype` now
  transparently supports both the official [PEP 586-compliant
  `typing.Literal` type hint][PEP 586] *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`.
* **[PEP 588][PEP 589]** (i.e., `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.*
* **[PEP 593][PEP 593]** (i.e., `Annotated`). `@beartype` now
  transparently supports both the official [PEP 593-compliant
  `typing.Annotated` type hint][PEP 593] *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.*

## Features Added

* **Subclass beartype validator.** The new
  `beartype.vale.IsSubclass[{type}]` beartype validator validates
  arbitrary objects and object attributes to be subclasses of the
  superclass `{type}`. Whereas the comparable [PEP 484][PEP
  484]-compliant `typing.Type[{type}]` and [PEP 585][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]]]]`).

## Features Deprecated

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

## Issues Resolved

* **Typed NumPy arrays subscripted by scalar NumPy ABCs** (e.g.,
  `numpy.typing.NDArray[numpy.floating]`), resolving issue #48 kindly
  submitted by @braniii the bran-eating brainiac. See above for details.
* **[PEP 563][PEP 563]-postponed relative forward references,**
  resolving issue #49 kindly submitted by the positive
  positron emitter and all-around stand-up statistics guru @posita. See
  above for details. Thanks to @posita for his ongoing commitment to
  awesome posita/dyce rolling type-checked by the growling bear!
* **[PEP 586][PEP 586] `typing_extensions.Literal` and [PEP 593][PEP
  593] `typing_extensions.Annotated` backports,**  resolving issue #52
  kindly submitted by cutting-edge Microsoft graph luminary @pbourke.
  See above for details.
* **[PEP 589][PEP 589] typed dictionaries** (i.e., `TypedDict`),
  resolving issue #55 kindly submitted by Kyle (@KyleKing), the
  undisputed King of Parexel AI Labs.
* **mypy-specific ~~logorrhea~~ valid complaints**, including:
  * **Implicit reexport complaints** `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 validator subscription complaints.** `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.
* **Windows-compatible testing,** kindly submitted by cutting-edge
  Microsoft luminary @pbourke. Previously, our `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.

## Tests Improved

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

  [PEP 484]: https://www.python.org/dev/peps/pep-0484/#id33
  [PEP 560]: https://www.python.org/dev/peps/pep-0560
  [PEP 563]: https://www.python.org/dev/peps/pep-0563
  [PEP 585]: https://www.python.org/dev/peps/pep-0585
  [PEP 586]: https://www.python.org/dev/peps/pep-0586
  [PEP 589]: https://www.python.org/dev/peps/pep-0589
  [PEP 593]: https://www.python.org/dev/peps/pep-0593
  [PEP 3119]: https://www.python.org/dev/peps/pep-3119/#overloading-isinstance-and-issubclass

(*Reticulated ticks gesticulate; ant antics masticate!*)
  • Loading branch information
leycec committed Oct 22, 2021
1 parent 9d52a0b commit 98576b4
Showing 1 changed file with 2 additions and 27 deletions.
29 changes: 2 additions & 27 deletions beartype/_util/cache/map/utilmapbig.py
Expand Up @@ -17,33 +17,8 @@
from typing import Callable, Dict

# ....................{ CLASSES }....................
#FIXME: Implement us up, please. This class intentionally does *NOT* subclass
#"dict", which is too low-level for thread-safe caching. All public API methods
#of this class need to behave thread-safely. For example, the canonical caching
#method typically resembles:
# def cache_entry(self, key: Hashable, value: object) -> object:
# with self._lock:
# if key not in self._dict:
# self._dict[key] = value
# return self._dict[key]
#FIXME: Actually, the above naive cache_entry() implementation fails to scale
#to the general case. Why? Because the whole point of caching, in general, is
#that computing the "value" to be cached is expensive; that's typically why
#you're caching it, right? Ergo, we can't simply pass "value" as a standard
#parameter above. We instead replace that with a caller-defined value factory:
# def cache_entry(
# self,
# key: Hashable,
# value_factory: Callable[[Hashable], Any],
# ) -> object:
# with self._lock:
# if key not in self._dict:
# self._dict[key] = value_factory(key)
# return self._dict[key]
#Although certainly feasible, the cost of calling that factory function on each
#cache miss could eclipse any benefits accrued from caching the values returned
#by that factory function. Maybe? Or... maybe not. In any case, that's it.

#FIXME: Submit back to StackOverflow, preferably under this question:
# https://stackoverflow.com/questions/1312331/using-a-global-dictionary-with-threads-in-python
class CacheUnboundedStrong(object):
'''
**Thread-safe strongly unbounded cache** (i.e., mapping of unlimited size
Expand Down

4 comments on commit 98576b4

@posita
Copy link
Collaborator

@posita posita commented on 98576b4 Oct 28, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Reticulated ticks gesticulate; ant antics masticate!)

I so very much wanted this to be a palendrome. 😄

@leycec
Copy link
Member Author

@leycec leycec commented on 98576b4 Oct 30, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good morning, Mr. Phelps. Your mission, should you choose to accept it, is to write a palindrome sentence generator from the radioactive tailing piles that trail beartype commit messages. I will then recursively feed palindromic sentences generated by that generator back into beartype commit messages, completing the disastrous cycle of death and rebirth. And this generator shall be known to PyPI only as... palindromepal.

Thus will palindromepal become beartype's first and only commit-time dependency. I am now expurgating into my own face. 🤮

@posita
Copy link
Collaborator

@posita posita commented on 98576b4 Oct 30, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I do that, and publish a corresponding GitHub Action integration to automate its feedback loop, do I have your permission to call that palindromepaldrone?

@leycec
Copy link
Member Author

@leycec leycec commented on 98576b4 Nov 1, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If palindromepaldrone exists, beartype will come to it in the night like that long-forgotten Spanish Facebook fling you hoped had finally forgotten about you suspected never would given the depth of what you shared by instant message. beartype will never forget about palindromepaldrone. Never.

Please sign in to comment.