From 98576b44c9ec8da894bbf1041eccea5515facc60 Mon Sep 17 00:00:00 2001 From: leycec Date: Fri, 22 Oct 2021 01:55:24 -0400 Subject: [PATCH] **Beartype 0.9.0** released. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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!?!? it's totally our fault * **[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!*) --- beartype/_util/cache/map/utilmapbig.py | 29 ++------------------------ 1 file changed, 2 insertions(+), 27 deletions(-) diff --git a/beartype/_util/cache/map/utilmapbig.py b/beartype/_util/cache/map/utilmapbig.py index 3a677a93..9973c4ef 100644 --- a/beartype/_util/cache/map/utilmapbig.py +++ b/beartype/_util/cache/map/utilmapbig.py @@ -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