summaryrefslogtreecommitdiffstats
path: root/contrib/python/propcache
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/python/propcache')
-rw-r--r--contrib/python/propcache/.dist-info/METADATA539
-rw-r--r--contrib/python/propcache/.dist-info/top_level.txt1
-rw-r--r--contrib/python/propcache/.yandex_meta/yamaker.yaml4
-rw-r--r--contrib/python/propcache/LICENSE202
-rw-r--r--contrib/python/propcache/NOTICE13
-rw-r--r--contrib/python/propcache/README.rst98
-rw-r--r--contrib/python/propcache/propcache/__init__.py32
-rw-r--r--contrib/python/propcache/propcache/_helpers.py39
-rw-r--r--contrib/python/propcache/propcache/_helpers_c.pyx103
-rw-r--r--contrib/python/propcache/propcache/_helpers_py.py64
-rw-r--r--contrib/python/propcache/propcache/api.py8
-rw-r--r--contrib/python/propcache/propcache/py.typed1
-rw-r--r--contrib/python/propcache/tests/conftest.py115
-rw-r--r--contrib/python/propcache/tests/test_api.py11
-rw-r--r--contrib/python/propcache/tests/test_cached_property.py226
-rw-r--r--contrib/python/propcache/tests/test_init.py43
-rw-r--r--contrib/python/propcache/tests/test_under_cached_property.py252
-rw-r--r--contrib/python/propcache/tests/ya.make11
-rw-r--r--contrib/python/propcache/ya.make34
19 files changed, 1796 insertions, 0 deletions
diff --git a/contrib/python/propcache/.dist-info/METADATA b/contrib/python/propcache/.dist-info/METADATA
new file mode 100644
index 00000000000..37cb00053d8
--- /dev/null
+++ b/contrib/python/propcache/.dist-info/METADATA
@@ -0,0 +1,539 @@
+Metadata-Version: 2.4
+Name: propcache
+Version: 0.5.2
+Summary: Accelerated property cache
+Home-page: https://github.com/aio-libs/propcache
+Author: Andrew Svetlov
+Author-email: [email protected]
+Maintainer: aiohttp team <[email protected]>
+Maintainer-email: [email protected]
+License: Apache-2.0
+Project-URL: Chat: Matrix, https://matrix.to/#/#aio-libs:matrix.org
+Project-URL: Chat: Matrix Space, https://matrix.to/#/#aio-libs-space:matrix.org
+Project-URL: CI: GitHub Workflows, https://github.com/aio-libs/propcache/actions?query=branch:master
+Project-URL: Code of Conduct, https://github.com/aio-libs/.github/blob/master/CODE_OF_CONDUCT.md
+Project-URL: Coverage: codecov, https://codecov.io/github/aio-libs/propcache
+Project-URL: Docs: Changelog, https://propcache.readthedocs.io/en/latest/changes/
+Project-URL: Docs: RTD, https://propcache.readthedocs.io
+Project-URL: GitHub: issues, https://github.com/aio-libs/propcache/issues
+Project-URL: GitHub: repo, https://github.com/aio-libs/propcache
+Keywords: cython,cext,propcache
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: Apache Software License
+Classifier: Programming Language :: Cython
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
+Classifier: Programming Language :: Python :: 3.12
+Classifier: Programming Language :: Python :: 3.13
+Classifier: Programming Language :: Python :: 3.14
+Classifier: Topic :: Internet :: WWW/HTTP
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Requires-Python: >=3.10
+Description-Content-Type: text/x-rst
+License-File: LICENSE
+License-File: NOTICE
+Dynamic: license-file
+
+propcache
+=========
+
+The module provides a fast implementation of cached properties for Python 3.10+.
+
+.. image:: https://github.com/aio-libs/propcache/actions/workflows/ci-cd.yml/badge.svg
+ :target: https://github.com/aio-libs/propcache/actions?query=workflow%3ACI
+ :align: right
+
+.. image:: https://codecov.io/gh/aio-libs/propcache/branch/master/graph/badge.svg
+ :target: https://codecov.io/gh/aio-libs/propcache
+
+.. image:: https://badge.fury.io/py/propcache.svg
+ :target: https://badge.fury.io/py/propcache
+
+
+.. image:: https://readthedocs.org/projects/propcache/badge/?version=latest
+ :target: https://propcache.readthedocs.io
+
+
+.. image:: https://img.shields.io/pypi/pyversions/propcache.svg
+ :target: https://pypi.python.org/pypi/propcache
+
+.. image:: https://img.shields.io/matrix/aio-libs:matrix.org?label=Discuss%20on%20Matrix%20at%20%23aio-libs%3Amatrix.org&logo=matrix&server_fqdn=matrix.org&style=flat
+ :target: https://matrix.to/#/%23aio-libs:matrix.org
+ :alt: Matrix Room — #aio-libs:matrix.org
+
+.. image:: https://img.shields.io/matrix/aio-libs-space:matrix.org?label=Discuss%20on%20Matrix%20at%20%23aio-libs-space%3Amatrix.org&logo=matrix&server_fqdn=matrix.org&style=flat
+ :target: https://matrix.to/#/%23aio-libs-space:matrix.org
+ :alt: Matrix Space — #aio-libs-space:matrix.org
+
+Introduction
+------------
+
+The API is designed to be nearly identical to the built-in ``functools.cached_property`` class,
+except for the additional ``under_cached_property`` class which uses ``self._cache``
+instead of ``self.__dict__`` to store the cached values and prevents ``__set__`` from being called.
+
+For full documentation please read https://propcache.readthedocs.io.
+
+Installation
+------------
+
+::
+
+ $ pip install propcache
+
+The library is Python 3 only!
+
+PyPI contains binary wheels for Linux, Windows and MacOS. If you want to install
+``propcache`` on another operating system where wheels are not provided,
+the the tarball will be used to compile the library from
+the source code. It requires a C compiler and and Python headers installed.
+
+To skip the compilation you must explicitly opt-in by using a PEP 517
+configuration setting ``pure-python``, or setting the ``PROPCACHE_NO_EXTENSIONS``
+environment variable to a non-empty value, e.g.:
+
+.. code-block:: console
+
+ $ pip install propcache --config-settings=pure-python=false
+
+Please note that the pure-Python (uncompiled) version is much slower. However,
+PyPy always uses a pure-Python implementation, and, as such, it is unaffected
+by this variable.
+
+
+API documentation
+------------------
+
+The documentation is located at https://propcache.readthedocs.io.
+
+Source code
+-----------
+
+The project is hosted on GitHub_
+
+Please file an issue on the `bug tracker
+<https://github.com/aio-libs/propcache/issues>`_ if you have found a bug
+or have some suggestion in order to improve the library.
+
+Discussion list
+---------------
+
+*aio-libs* google group: https://groups.google.com/forum/#!forum/aio-libs
+
+Feel free to post your questions and ideas here.
+
+
+Authors and License
+-------------------
+
+The ``propcache`` package is derived from ``yarl`` which is written by Andrew Svetlov.
+
+It's *Apache 2* licensed and freely available.
+
+
+.. _GitHub: https://github.com/aio-libs/propcache
+
+=========
+Changelog
+=========
+
+..
+ You should *NOT* be adding new change log entries to this file, this
+ file is managed by towncrier. You *may* edit previous change logs to
+ fix problems like typo corrections or such.
+ To add a new change log entry, please see
+ https://pip.pypa.io/en/latest/development/#adding-a-news-entry
+ we named the news folder "changes".
+
+ WARNING: Don't drop the next directive!
+
+.. towncrier release notes start
+
+0.5.2
+=====
+
+*(2026-05-08)*
+
+
+No significant changes.
+
+
+----
+
+
+0.5.1
+=====
+
+*(2026-05-08)*
+
+
+No significant changes.
+
+
+----
+
+
+0.5.0
+=====
+
+*(2026-05-08)*
+
+
+Features
+--------
+
+- Added support for newer type hints and remove ``Optional`` and ``Union`` from all annotations
+ -- by `@Vizonex <https://github.com/sponsors/Vizonex>`__
+
+ *Related issues and pull requests on GitHub:*
+ `#193 <https://github.com/aio-libs/propcache/issues/193>`__.
+
+
+Removals and backward incompatible breaking changes
+---------------------------------------------------
+
+- Dropped support for Python 3.9 as it has reached end of life.
+
+ *Related issues and pull requests on GitHub:*
+ `#216 <https://github.com/aio-libs/propcache/issues/216>`__.
+
+
+Packaging updates and notes for downstreams
+-------------------------------------------
+
+- Changed the Cython build dependency from ``~= 3.1.0`` to ``>= 3.2.0``,
+ removing the upper version bound to avoid conflicts for downstream packagers
+ -- by `@jameshilliard <https://github.com/sponsors/jameshilliard>`__ and `@gundalow <https://github.com/sponsors/gundalow>`__.
+
+ The upstream Cython version is pinned to 3.2.4 in the CI/CD environment.
+
+ *Related issues and pull requests on GitHub:*
+ `#184 <https://github.com/aio-libs/propcache/issues/184>`__, `#188 <https://github.com/aio-libs/propcache/issues/188>`__, `#214 <https://github.com/aio-libs/propcache/issues/214>`__.
+
+- Start building and shipping riscv64 wheels
+ -- by `@justeph <https://github.com/sponsors/justeph>`__.
+
+ *Related issues and pull requests on GitHub:*
+ `#194 <https://github.com/aio-libs/propcache/issues/194>`__.
+
+- The `PEP 517 <https://peps.python.org/pep-517>`__ build backend now supports a new ``build-inplace``
+ config setting (and ``PROPCACHE_BUILD_INPLACE`` environment variable)
+ for controlling whether to build the project in-tree or in a
+ temporary directory. It only affects wheels and is set up to build
+ in a temporary directory by default. It does not affect editable
+ wheel builds; they will keep being built in-tree regardless.
+
+ Here's an example of using this setting:
+
+ .. code-block:: console
+
+ $ python -m build --config-setting=build-inplace=true
+
+ Additionally, when building wheels in an automatically created
+ temporary directory, the build backend now normalizes the
+ respective file system path to a deterministic source checkout
+ directory by injecting the ``-ffile-prefix-map`` compiler option
+ into the ``CFLAGS`` environment variable, as suggested by known
+ `reproducible build practices
+ <https://reproducible-builds.org/docs/build-path/>`__.
+
+ The effect is that downstreams will get reproducible build results.
+
+ *Related issues and pull requests on GitHub:*
+ `#218 <https://github.com/aio-libs/propcache/issues/218>`__.
+
+
+----
+
+
+0.4.1
+=====
+
+*(2025-10-08)*
+
+
+Bug fixes
+---------
+
+- Fixed reference leak caused by ``Py_INCREF`` because Cython has its own reference counter systems -- by `@Vizonex <https://github.com/sponsors/Vizonex>`__.
+
+ *Related issues and pull requests on GitHub:*
+ `#162 <https://github.com/aio-libs/propcache/issues/162>`__.
+
+
+Contributor-facing changes
+--------------------------
+
+- Fixes the default value for the ``os``
+ parameter in ``reusable-build-wheel.yml``
+ to be ``ubuntu-latest`` instead of
+ ``ubuntu``.
+
+ *Related issues and pull requests on GitHub:*
+ `#155 <https://github.com/aio-libs/propcache/issues/155>`__.
+
+
+----
+
+
+0.4.0
+=====
+
+*(2025-10-04)*
+
+
+Features
+--------
+
+- Optimized propcache by replacing sentinel ``object`` for checking if
+ the ``object`` is ``NULL`` and changed ``dict`` API for
+ Python C-API -- by `@Vizonex <https://github.com/sponsors/Vizonex>`__.
+
+ *Related issues and pull requests on GitHub:*
+ `#121 <https://github.com/aio-libs/propcache/issues/121>`__.
+
+
+Contributor-facing changes
+--------------------------
+
+- Builds have been added for arm64 Windows
+ wheels and the ``reusable-build-wheel.yml``
+ workflow has been modified to allow for
+ an OS value (``windows-11-arm``) which
+ does not include the ``-latest`` postfix
+ -- by `@finnagin <https://github.com/sponsors/finnagin>`__.
+
+ *Related issues and pull requests on GitHub:*
+ `#133 <https://github.com/aio-libs/propcache/issues/133>`__.
+
+- Added CI for CPython 3.14 -- by `@kumaraditya303 <https://github.com/sponsors/kumaraditya303>`__.
+
+ *Related issues and pull requests on GitHub:*
+ `#140 <https://github.com/aio-libs/propcache/issues/140>`__.
+
+
+----
+
+
+0.3.2
+=====
+
+*(2025-06-09)*
+
+
+Improved documentation
+----------------------
+
+- Fixed incorrect decorator usage in the ``~propcache.api.under_cached_property`` example code -- by `@meanmail <https://github.com/sponsors/meanmail>`__.
+
+ *Related issues and pull requests on GitHub:*
+ `#109 <https://github.com/aio-libs/propcache/issues/109>`__.
+
+
+Packaging updates and notes for downstreams
+-------------------------------------------
+
+- Updated to use Cython 3.1 universally across the build path -- by `@lysnikolaou <https://github.com/sponsors/lysnikolaou>`__.
+
+ *Related issues and pull requests on GitHub:*
+ `#117 <https://github.com/aio-libs/propcache/issues/117>`__.
+
+- Made Cython line tracing opt-in via the ``with-cython-tracing`` build config setting -- by `@bdraco <https://github.com/sponsors/bdraco>`__.
+
+ Previously, line tracing was enabled by default in ``pyproject.toml``, which caused build issues for some users and made wheels nearly twice as slow.
+
+ Now line tracing is only enabled when explicitly requested via ``pip install . --config-setting=with-cython-tracing=true`` or by setting the ``PROPCACHE_CYTHON_TRACING`` environment variable.
+
+ *Related issues and pull requests on GitHub:*
+ `#118 <https://github.com/aio-libs/propcache/issues/118>`__.
+
+
+----
+
+
+0.3.1
+=====
+
+*(2025-03-25)*
+
+
+Bug fixes
+---------
+
+- Improved typing annotations, fixing some type errors under correct usage
+ and improving typing robustness generally -- by `@Dreamsorcerer <https://github.com/sponsors/Dreamsorcerer>`__.
+
+ *Related issues and pull requests on GitHub:*
+ `#103 <https://github.com/aio-libs/propcache/issues/103>`__.
+
+
+----
+
+
+0.3.0
+=====
+
+*(2025-02-20)*
+
+
+Features
+--------
+
+- Implemented support for the free-threaded build of CPython 3.13 -- by `@lysnikolaou <https://github.com/sponsors/lysnikolaou>`__.
+
+ *Related issues and pull requests on GitHub:*
+ `#84 <https://github.com/aio-libs/propcache/issues/84>`__.
+
+
+Packaging updates and notes for downstreams
+-------------------------------------------
+
+- Started building wheels for the free-threaded build of CPython 3.13 -- by `@lysnikolaou <https://github.com/sponsors/lysnikolaou>`__.
+
+ *Related issues and pull requests on GitHub:*
+ `#84 <https://github.com/aio-libs/propcache/issues/84>`__.
+
+
+Contributor-facing changes
+--------------------------
+
+- GitHub Actions CI/CD is now configured to manage caching pip-ecosystem
+ dependencies using `re-actors/cache-python-deps`_ -- an action by
+ `@webknjaz <https://github.com/sponsors/webknjaz>`__ that takes into account ABI stability and the exact
+ version of Python runtime.
+
+ .. _`re-actors/cache-python-deps`:
+ https://github.com/marketplace/actions/cache-python-deps
+
+ *Related issues and pull requests on GitHub:*
+ `#93 <https://github.com/aio-libs/propcache/issues/93>`__.
+
+
+----
+
+
+0.2.1
+=====
+
+*(2024-12-01)*
+
+
+Bug fixes
+---------
+
+- Stopped implicitly allowing the use of Cython pre-release versions when
+ building the distribution package -- by `@ajsanchezsanz <https://github.com/sponsors/ajsanchezsanz>`__ and
+ `@markgreene74 <https://github.com/sponsors/markgreene74>`__.
+
+ *Related commits on GitHub:*
+ `64df0a6 <https://github.com/aio-libs/propcache/commit/64df0a6>`__.
+
+- Fixed ``wrapped`` and ``func`` not being accessible in the Cython versions of ``propcache.api.cached_property`` and ``propcache.api.under_cached_property`` decorators -- by `@bdraco <https://github.com/sponsors/bdraco>`__.
+
+ *Related issues and pull requests on GitHub:*
+ `#72 <https://github.com/aio-libs/propcache/issues/72>`__.
+
+
+Removals and backward incompatible breaking changes
+---------------------------------------------------
+
+- Removed support for Python 3.8 as it has reached end of life -- by `@bdraco <https://github.com/sponsors/bdraco>`__.
+
+ *Related issues and pull requests on GitHub:*
+ `#57 <https://github.com/aio-libs/propcache/issues/57>`__.
+
+
+Packaging updates and notes for downstreams
+-------------------------------------------
+
+- Stopped implicitly allowing the use of Cython pre-release versions when
+ building the distribution package -- by `@ajsanchezsanz <https://github.com/sponsors/ajsanchezsanz>`__ and
+ `@markgreene74 <https://github.com/sponsors/markgreene74>`__.
+
+ *Related commits on GitHub:*
+ `64df0a6 <https://github.com/aio-libs/propcache/commit/64df0a6>`__.
+
+
+----
+
+
+0.2.0
+=====
+
+*(2024-10-07)*
+
+
+Bug fixes
+---------
+
+- Fixed loading the C-extensions on Python 3.8 -- by `@bdraco <https://github.com/sponsors/bdraco>`__.
+
+ *Related issues and pull requests on GitHub:*
+ `#26 <https://github.com/aio-libs/propcache/issues/26>`__.
+
+
+Features
+--------
+
+- Improved typing for the ``propcache.api.under_cached_property`` decorator -- by `@bdraco <https://github.com/sponsors/bdraco>`__.
+
+ *Related issues and pull requests on GitHub:*
+ `#38 <https://github.com/aio-libs/propcache/issues/38>`__.
+
+
+Improved documentation
+----------------------
+
+- Added API documentation for the ``propcache.api.cached_property`` and ``propcache.api.under_cached_property`` decorators -- by `@bdraco <https://github.com/sponsors/bdraco>`__.
+
+ *Related issues and pull requests on GitHub:*
+ `#16 <https://github.com/aio-libs/propcache/issues/16>`__.
+
+
+Packaging updates and notes for downstreams
+-------------------------------------------
+
+- Moved ``propcache.api.under_cached_property`` and ``propcache.api.cached_property`` to `propcache.api` -- by `@bdraco <https://github.com/sponsors/bdraco>`__.
+
+ Both decorators remain importable from the top-level package, however importing from `propcache.api` is now the recommended way to use them.
+
+ *Related issues and pull requests on GitHub:*
+ `#19 <https://github.com/aio-libs/propcache/issues/19>`__, `#24 <https://github.com/aio-libs/propcache/issues/24>`__, `#32 <https://github.com/aio-libs/propcache/issues/32>`__.
+
+- Converted project to use a src layout -- by `@bdraco <https://github.com/sponsors/bdraco>`__.
+
+ *Related issues and pull requests on GitHub:*
+ `#22 <https://github.com/aio-libs/propcache/issues/22>`__, `#29 <https://github.com/aio-libs/propcache/issues/29>`__, `#37 <https://github.com/aio-libs/propcache/issues/37>`__.
+
+
+----
+
+
+0.1.0
+=====
+
+*(2024-10-03)*
+
+
+Features
+--------
+
+- Added ``armv7l`` wheels -- by `@bdraco <https://github.com/sponsors/bdraco>`__.
+
+ *Related issues and pull requests on GitHub:*
+ `#5 <https://github.com/aio-libs/propcache/issues/5>`__.
+
+
+----
+
+
+0.0.0
+=====
+
+*(2024-10-02)*
+
+
+- Initial release.
diff --git a/contrib/python/propcache/.dist-info/top_level.txt b/contrib/python/propcache/.dist-info/top_level.txt
new file mode 100644
index 00000000000..8c9accf6226
--- /dev/null
+++ b/contrib/python/propcache/.dist-info/top_level.txt
@@ -0,0 +1 @@
+propcache
diff --git a/contrib/python/propcache/.yandex_meta/yamaker.yaml b/contrib/python/propcache/.yandex_meta/yamaker.yaml
new file mode 100644
index 00000000000..4daee40a45c
--- /dev/null
+++ b/contrib/python/propcache/.yandex_meta/yamaker.yaml
@@ -0,0 +1,4 @@
+mark_as_cython_c:
+ - propcache/_helpers_c.pyx
+exclude:
+ - tests/test_benchmarks.py
diff --git a/contrib/python/propcache/LICENSE b/contrib/python/propcache/LICENSE
new file mode 100644
index 00000000000..d6456956733
--- /dev/null
+++ b/contrib/python/propcache/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/contrib/python/propcache/NOTICE b/contrib/python/propcache/NOTICE
new file mode 100644
index 00000000000..fa53b2b138d
--- /dev/null
+++ b/contrib/python/propcache/NOTICE
@@ -0,0 +1,13 @@
+ Copyright 2016-2021, Andrew Svetlov and aio-libs team
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/contrib/python/propcache/README.rst b/contrib/python/propcache/README.rst
new file mode 100644
index 00000000000..ce10fbf8037
--- /dev/null
+++ b/contrib/python/propcache/README.rst
@@ -0,0 +1,98 @@
+propcache
+=========
+
+The module provides a fast implementation of cached properties for Python 3.10+.
+
+.. image:: https://github.com/aio-libs/propcache/actions/workflows/ci-cd.yml/badge.svg
+ :target: https://github.com/aio-libs/propcache/actions?query=workflow%3ACI
+ :align: right
+
+.. image:: https://codecov.io/gh/aio-libs/propcache/branch/master/graph/badge.svg
+ :target: https://codecov.io/gh/aio-libs/propcache
+
+.. image:: https://badge.fury.io/py/propcache.svg
+ :target: https://badge.fury.io/py/propcache
+
+
+.. image:: https://readthedocs.org/projects/propcache/badge/?version=latest
+ :target: https://propcache.readthedocs.io
+
+
+.. image:: https://img.shields.io/pypi/pyversions/propcache.svg
+ :target: https://pypi.python.org/pypi/propcache
+
+.. image:: https://img.shields.io/matrix/aio-libs:matrix.org?label=Discuss%20on%20Matrix%20at%20%23aio-libs%3Amatrix.org&logo=matrix&server_fqdn=matrix.org&style=flat
+ :target: https://matrix.to/#/%23aio-libs:matrix.org
+ :alt: Matrix Room — #aio-libs:matrix.org
+
+.. image:: https://img.shields.io/matrix/aio-libs-space:matrix.org?label=Discuss%20on%20Matrix%20at%20%23aio-libs-space%3Amatrix.org&logo=matrix&server_fqdn=matrix.org&style=flat
+ :target: https://matrix.to/#/%23aio-libs-space:matrix.org
+ :alt: Matrix Space — #aio-libs-space:matrix.org
+
+Introduction
+------------
+
+The API is designed to be nearly identical to the built-in ``functools.cached_property`` class,
+except for the additional ``under_cached_property`` class which uses ``self._cache``
+instead of ``self.__dict__`` to store the cached values and prevents ``__set__`` from being called.
+
+For full documentation please read https://propcache.readthedocs.io.
+
+Installation
+------------
+
+::
+
+ $ pip install propcache
+
+The library is Python 3 only!
+
+PyPI contains binary wheels for Linux, Windows and MacOS. If you want to install
+``propcache`` on another operating system where wheels are not provided,
+the the tarball will be used to compile the library from
+the source code. It requires a C compiler and and Python headers installed.
+
+To skip the compilation you must explicitly opt-in by using a PEP 517
+configuration setting ``pure-python``, or setting the ``PROPCACHE_NO_EXTENSIONS``
+environment variable to a non-empty value, e.g.:
+
+.. code-block:: console
+
+ $ pip install propcache --config-settings=pure-python=false
+
+Please note that the pure-Python (uncompiled) version is much slower. However,
+PyPy always uses a pure-Python implementation, and, as such, it is unaffected
+by this variable.
+
+
+API documentation
+------------------
+
+The documentation is located at https://propcache.readthedocs.io.
+
+Source code
+-----------
+
+The project is hosted on GitHub_
+
+Please file an issue on the `bug tracker
+<https://github.com/aio-libs/propcache/issues>`_ if you have found a bug
+or have some suggestion in order to improve the library.
+
+Discussion list
+---------------
+
+*aio-libs* google group: https://groups.google.com/forum/#!forum/aio-libs
+
+Feel free to post your questions and ideas here.
+
+
+Authors and License
+-------------------
+
+The ``propcache`` package is derived from ``yarl`` which is written by Andrew Svetlov.
+
+It's *Apache 2* licensed and freely available.
+
+
+.. _GitHub: https://github.com/aio-libs/propcache
diff --git a/contrib/python/propcache/propcache/__init__.py b/contrib/python/propcache/propcache/__init__.py
new file mode 100644
index 00000000000..bbfd78a3628
--- /dev/null
+++ b/contrib/python/propcache/propcache/__init__.py
@@ -0,0 +1,32 @@
+"""propcache: An accelerated property cache for Python classes."""
+
+from typing import TYPE_CHECKING
+
+_PUBLIC_API = ("cached_property", "under_cached_property")
+
+__version__ = "0.5.2"
+__all__ = ()
+
+# Imports have moved to `propcache.api` in 0.2.0+.
+# This module is now a facade for the API.
+if TYPE_CHECKING:
+ from .api import cached_property as cached_property # noqa: F401
+ from .api import under_cached_property as under_cached_property # noqa: F401
+
+
+def _import_facade(attr: str) -> object:
+ """Import the public API from the `api` module."""
+ if attr in _PUBLIC_API:
+ from . import api # pylint: disable=import-outside-toplevel
+
+ return getattr(api, attr)
+ raise AttributeError(f"module '{__package__}' has no attribute '{attr}'")
+
+
+def _dir_facade() -> list[str]:
+ """Include the public API in the module's dir() output."""
+ return [*_PUBLIC_API, *globals().keys()]
+
+
+__getattr__ = _import_facade
+__dir__ = _dir_facade
diff --git a/contrib/python/propcache/propcache/_helpers.py b/contrib/python/propcache/propcache/_helpers.py
new file mode 100644
index 00000000000..1e52895c151
--- /dev/null
+++ b/contrib/python/propcache/propcache/_helpers.py
@@ -0,0 +1,39 @@
+import os
+import sys
+from typing import TYPE_CHECKING
+
+__all__ = ("cached_property", "under_cached_property")
+
+
+NO_EXTENSIONS = bool(os.environ.get("PROPCACHE_NO_EXTENSIONS")) # type: bool
+if sys.implementation.name != "cpython":
+ NO_EXTENSIONS = True
+
+
+# isort: off
+if TYPE_CHECKING:
+ from ._helpers_py import cached_property as cached_property_py
+ from ._helpers_py import under_cached_property as under_cached_property_py
+
+ cached_property = cached_property_py
+ under_cached_property = under_cached_property_py
+elif not NO_EXTENSIONS: # pragma: no branch
+ try:
+ from ._helpers_c import cached_property as cached_property_c # type: ignore[attr-defined, unused-ignore]
+ from ._helpers_c import under_cached_property as under_cached_property_c # type: ignore[attr-defined, unused-ignore]
+
+ cached_property = cached_property_c
+ under_cached_property = under_cached_property_c
+ except ImportError: # pragma: no cover
+ from ._helpers_py import cached_property as cached_property_py
+ from ._helpers_py import under_cached_property as under_cached_property_py
+
+ cached_property = cached_property_py # type: ignore[assignment, misc]
+ under_cached_property = under_cached_property_py
+else:
+ from ._helpers_py import cached_property as cached_property_py
+ from ._helpers_py import under_cached_property as under_cached_property_py
+
+ cached_property = cached_property_py # type: ignore[assignment, misc]
+ under_cached_property = under_cached_property_py
+# isort: on
diff --git a/contrib/python/propcache/propcache/_helpers_c.pyx b/contrib/python/propcache/propcache/_helpers_c.pyx
new file mode 100644
index 00000000000..9e9e5589b95
--- /dev/null
+++ b/contrib/python/propcache/propcache/_helpers_c.pyx
@@ -0,0 +1,103 @@
+# cython: language_level=3, freethreading_compatible=True
+from types import GenericAlias
+
+from cpython.dict cimport PyDict_GetItem
+from cpython.object cimport PyObject
+
+
+cdef extern from "Python.h":
+ # Call a callable Python object callable with exactly
+ # 1 positional argument arg and no keyword arguments.
+ # Return the result of the call on success, or raise
+ # an exception and return NULL on failure.
+ PyObject* PyObject_CallOneArg(
+ object callable, object arg
+ ) except NULL
+ int PyDict_SetItem(
+ object dict, object key, PyObject* value
+ ) except -1
+ void Py_DECREF(PyObject*)
+
+
+cdef class under_cached_property:
+ """Use as a class method decorator. It operates almost exactly like
+ the Python `@property` decorator, but it puts the result of the
+ method it decorates into the instance dict after the first call,
+ effectively replacing the function it decorates with an instance
+ variable. It is, in Python parlance, a data descriptor.
+
+ """
+
+ cdef readonly object wrapped
+ cdef object name
+
+ def __init__(self, object wrapped):
+ self.wrapped = wrapped
+ self.name = wrapped.__name__
+
+ @property
+ def __doc__(self):
+ return self.wrapped.__doc__
+
+ def __get__(self, object inst, owner):
+ if inst is None:
+ return self
+ cdef dict cache = inst._cache
+ cdef PyObject* val = PyDict_GetItem(cache, self.name)
+ if val == NULL:
+ val = PyObject_CallOneArg(self.wrapped, inst)
+ PyDict_SetItem(cache, self.name, val)
+ Py_DECREF(val)
+ return <object>val
+
+ def __set__(self, inst, value):
+ raise AttributeError("cached property is read-only")
+
+ __class_getitem__ = classmethod(GenericAlias)
+
+
+cdef class cached_property:
+ """Use as a class method decorator. It operates almost exactly like
+ the Python `@property` decorator, but it puts the result of the
+ method it decorates into the instance dict after the first call,
+ effectively replacing the function it decorates with an instance
+ variable. It is, in Python parlance, a data descriptor.
+
+ """
+
+ cdef readonly object func
+ cdef object name
+
+ def __init__(self, func):
+ self.func = func
+ self.name = None
+
+ @property
+ def __doc__(self):
+ return self.func.__doc__
+
+ def __set_name__(self, owner, object name):
+ if self.name is None:
+ self.name = name
+ elif name != self.name:
+ raise TypeError(
+ "Cannot assign the same cached_property to two different names "
+ f"({self.name!r} and {name!r})."
+ )
+
+ def __get__(self, inst, owner):
+ if inst is None:
+ return self
+ if self.name is None:
+ raise TypeError(
+ "Cannot use cached_property instance"
+ " without calling __set_name__ on it.")
+ cdef dict cache = inst.__dict__
+ cdef PyObject* val = PyDict_GetItem(cache, self.name)
+ if val is NULL:
+ val = PyObject_CallOneArg(self.func, inst)
+ PyDict_SetItem(cache, self.name, val)
+ Py_DECREF(val)
+ return <object>val
+
+ __class_getitem__ = classmethod(GenericAlias)
diff --git a/contrib/python/propcache/propcache/_helpers_py.py b/contrib/python/propcache/propcache/_helpers_py.py
new file mode 100644
index 00000000000..db5b8d6378f
--- /dev/null
+++ b/contrib/python/propcache/propcache/_helpers_py.py
@@ -0,0 +1,64 @@
+"""Various helper functions."""
+
+from __future__ import annotations
+
+import sys
+from collections.abc import Callable, Mapping
+from functools import cached_property
+from typing import Any, Generic, Protocol, TypeVar, overload
+
+__all__ = ("under_cached_property", "cached_property")
+
+
+if sys.version_info >= (3, 11):
+ from typing import Self
+else:
+ Self = Any
+
+_T = TypeVar("_T")
+# We use Mapping to make it possible to use TypedDict, but this isn't
+# technically type safe as we need to assign into the dict.
+_Cache = TypeVar("_Cache", bound=Mapping[str, Any])
+
+
+class _CacheImpl(Protocol[_Cache]):
+ _cache: _Cache
+
+
+class under_cached_property(Generic[_T]):
+ """Use as a class method decorator.
+
+ It operates almost exactly like
+ the Python `@property` decorator, but it puts the result of the
+ method it decorates into the instance dict after the first call,
+ effectively replacing the function it decorates with an instance
+ variable. It is, in Python parlance, a data descriptor.
+ """
+
+ def __init__(self, wrapped: Callable[[Any], _T]) -> None:
+ self.wrapped = wrapped
+ self.__doc__ = wrapped.__doc__
+ self.name = wrapped.__name__
+
+ @overload
+ def __get__(self, inst: None, owner: type[object] | None = None) -> Self: ...
+
+ @overload
+ def __get__(
+ self, inst: _CacheImpl[Any], owner: type[object] | None = None
+ ) -> _T: ...
+
+ def __get__(
+ self, inst: _CacheImpl[Any] | None, owner: type[object] | None = None
+ ) -> _T | Self:
+ if inst is None:
+ return self
+ try:
+ return inst._cache[self.name] # type: ignore[no-any-return]
+ except KeyError:
+ val = self.wrapped(inst)
+ inst._cache[self.name] = val
+ return val
+
+ def __set__(self, inst: _CacheImpl[Any], value: _T) -> None:
+ raise AttributeError("cached property is read-only")
diff --git a/contrib/python/propcache/propcache/api.py b/contrib/python/propcache/propcache/api.py
new file mode 100644
index 00000000000..22389e6337f
--- /dev/null
+++ b/contrib/python/propcache/propcache/api.py
@@ -0,0 +1,8 @@
+"""Public API of the property caching library."""
+
+from ._helpers import cached_property, under_cached_property
+
+__all__ = (
+ "cached_property",
+ "under_cached_property",
+)
diff --git a/contrib/python/propcache/propcache/py.typed b/contrib/python/propcache/propcache/py.typed
new file mode 100644
index 00000000000..dcf2c804da5
--- /dev/null
+++ b/contrib/python/propcache/propcache/py.typed
@@ -0,0 +1 @@
+# Placeholder
diff --git a/contrib/python/propcache/tests/conftest.py b/contrib/python/propcache/tests/conftest.py
new file mode 100644
index 00000000000..ee43f339231
--- /dev/null
+++ b/contrib/python/propcache/tests/conftest.py
@@ -0,0 +1,115 @@
+import argparse
+from dataclasses import dataclass
+from functools import cached_property
+from importlib import import_module
+from types import ModuleType
+
+import pytest
+
+C_EXT_MARK = pytest.mark.c_extension
+
+
+@dataclass(frozen=True)
+class PropcacheImplementation:
+ """A facade for accessing importable propcache module variants.
+
+ An instance essentially represents a c-extension or a pure-python module.
+ The actual underlying module is accessed dynamically through a property and
+ is cached.
+
+ It also has a text tag depending on what variant it is, and a string
+ representation suitable for use in Pytest's test IDs via parametrization.
+ """
+
+ is_pure_python: bool
+ """A flag showing whether this is a pure-python module or a C-extension."""
+
+ @cached_property
+ def tag(self) -> str:
+ """Return a text representation of the pure-python attribute."""
+ return "pure-python" if self.is_pure_python else "c-extension"
+
+ @cached_property
+ def imported_module(self) -> ModuleType:
+ """Return a loaded importable containing a propcache variant."""
+ importable_module = "_helpers_py" if self.is_pure_python else "_helpers_c"
+ return import_module(f"propcache.{importable_module}")
+
+ def __str__(self) -> str:
+ """Render the implementation facade instance as a string."""
+ return f"{self.tag}-module"
+
+
+ scope="session",
+ params=(
+ pytest.param(
+ PropcacheImplementation(is_pure_python=False),
+ marks=C_EXT_MARK,
+ ),
+ PropcacheImplementation(is_pure_python=True),
+ ),
+ ids=str,
+)
+def propcache_implementation(request: pytest.FixtureRequest) -> PropcacheImplementation:
+ """Return a propcache variant facade."""
+ return request.param # type: ignore[no-any-return]
+
+
[email protected](scope="session")
+def propcache_module(
+ propcache_implementation: PropcacheImplementation,
+) -> ModuleType:
+ """Return a pre-imported module containing a propcache variant."""
+ return propcache_implementation.imported_module
+
+
+def pytest_addoption(
+ parser: pytest.Parser,
+ pluginmanager: pytest.PytestPluginManager,
+) -> None:
+ """Define a new ``--c-extensions`` flag.
+
+ This lets the callers deselect tests executed against the C-extension
+ version of the ``propcache`` implementation.
+ """
+ del pluginmanager
+ parser.addoption(
+ "--c-extensions", # disabled with `--no-c-extensions`
+ action=argparse.BooleanOptionalAction,
+ default=True,
+ dest="c_extensions",
+ help="Test C-extensions (on by default)",
+ )
+
+
+def pytest_collection_modifyitems(
+ session: pytest.Session,
+ config: pytest.Config,
+ items: list[pytest.Item],
+) -> None:
+ """Deselect tests against C-extensions when requested via CLI."""
+ test_c_extensions = config.getoption("--c-extensions") is True
+
+ if test_c_extensions:
+ return
+
+ selected_tests: list[pytest.Item] = []
+ deselected_tests: list[pytest.Item] = []
+
+ for item in items:
+ c_ext = item.get_closest_marker(C_EXT_MARK.name) is not None
+
+ target_items_list = deselected_tests if c_ext else selected_tests
+ target_items_list.append(item)
+
+ config.hook.pytest_deselected(items=deselected_tests)
+ items[:] = selected_tests
+
+
+def pytest_configure(config: pytest.Config) -> None:
+ """Declare the C-extension marker in config."""
+ config.addinivalue_line(
+ "markers",
+ f"{C_EXT_MARK.name}: tests running against the C-extension implementation.",
+ )
diff --git a/contrib/python/propcache/tests/test_api.py b/contrib/python/propcache/tests/test_api.py
new file mode 100644
index 00000000000..85beec3606a
--- /dev/null
+++ b/contrib/python/propcache/tests/test_api.py
@@ -0,0 +1,11 @@
+"""Test we do not break the public API."""
+
+from propcache import _helpers, api
+
+
+def test_api() -> None:
+ """Verify the public API is accessible."""
+ assert api.cached_property is not None
+ assert api.under_cached_property is not None
+ assert api.cached_property is _helpers.cached_property
+ assert api.under_cached_property is _helpers.under_cached_property
diff --git a/contrib/python/propcache/tests/test_cached_property.py b/contrib/python/propcache/tests/test_cached_property.py
new file mode 100644
index 00000000000..a46c715c31b
--- /dev/null
+++ b/contrib/python/propcache/tests/test_cached_property.py
@@ -0,0 +1,226 @@
+import gc
+import sys
+from collections.abc import Callable
+from operator import not_
+from typing import TYPE_CHECKING, Any, Protocol, TypeVar
+
+import pytest
+
+from propcache.api import cached_property
+
+IS_PYPY = hasattr(sys, "pypy_version_info")
+
+if sys.version_info >= (3, 11):
+ from typing import assert_type
+
+_T_co = TypeVar("_T_co", covariant=True)
+
+
+class APIProtocol(Protocol):
+ def cached_property(
+ self, func: Callable[[Any], _T_co]
+ ) -> cached_property[_T_co]: ...
+
+
+def test_cached_property(propcache_module: APIProtocol) -> None:
+ class A:
+ def __init__(self) -> None:
+ """Init."""
+
+ @propcache_module.cached_property
+ def prop(self) -> int:
+ return 1
+
+ a = A()
+ if sys.version_info >= (3, 11):
+ assert_type(a.prop, int)
+ assert a.prop == 1
+
+
+def test_cached_property_without_cache(propcache_module: APIProtocol) -> None:
+ class A:
+
+ __slots__ = ()
+
+ def __init__(self) -> None:
+ pass
+
+ @propcache_module.cached_property
+ def prop(self) -> None:
+ """Mock property."""
+
+ a = A()
+
+ with pytest.raises(AttributeError):
+ a.prop = 123 # type: ignore[assignment]
+
+
+def test_cached_property_check_without_cache(propcache_module: APIProtocol) -> None:
+ class A:
+
+ __slots__ = ()
+
+ def __init__(self) -> None:
+ """Init."""
+
+ @propcache_module.cached_property
+ def prop(self) -> None:
+ """Mock property."""
+
+ a = A()
+ with pytest.raises((TypeError, AttributeError)):
+ assert a.prop == 1
+
+
+def test_cached_property_caching(propcache_module: APIProtocol) -> None:
+ class A:
+ def __init__(self) -> None:
+ """Init."""
+
+ @propcache_module.cached_property
+ def prop(self) -> int:
+ """Docstring."""
+ return 1
+
+ a = A()
+ assert a.prop == 1
+
+
+def test_cached_property_class_docstring(propcache_module: APIProtocol) -> None:
+ class A:
+ def __init__(self) -> None:
+ """Init."""
+
+ @propcache_module.cached_property
+ def prop(self) -> None:
+ """Docstring."""
+
+ if TYPE_CHECKING:
+ assert isinstance(A.prop, cached_property)
+ else:
+ assert isinstance(A.prop, propcache_module.cached_property)
+ assert "Docstring." == A.prop.__doc__
+
+
+def test_set_name(propcache_module: APIProtocol) -> None:
+ """Test that the __set_name__ method is called and checked."""
+
+ class A:
+
+ @propcache_module.cached_property
+ def prop(self) -> None:
+ """Docstring."""
+
+ A.prop.__set_name__(A, "prop")
+
+ match = r"Cannot assign the same cached_property to two "
+ with pytest.raises(TypeError, match=match):
+ A.prop.__set_name__(A, "something_else")
+
+
+def test_get_without_set_name(propcache_module: APIProtocol) -> None:
+ """Test that get without __set_name__ fails."""
+ cp = propcache_module.cached_property(not_)
+
+ class A:
+ """A class."""
+
+ A.cp = cp # type: ignore[attr-defined]
+ match = r"Cannot use cached_property instance "
+ with pytest.raises(TypeError, match=match):
+ _ = A().cp # type: ignore[attr-defined]
+
+
+def test_ensured_wrapped_function_is_accessible(propcache_module: APIProtocol) -> None:
+ """Test that the wrapped function can be accessed from python."""
+
+ class A:
+ def __init__(self) -> None:
+ """Init."""
+
+ @propcache_module.cached_property
+ def prop(self) -> int:
+ """Docstring."""
+ return 1
+
+ a = A()
+ assert A.prop.func(a) == 1
+
+
[email protected](IS_PYPY, reason="PyPy has no C extension")
+def test_cached_property_no_refcount_leak(propcache_module: APIProtocol) -> None:
+ """Test that cached_property does not leak references."""
+
+ class CachedPropertySentinel:
+ """A unique object we can track."""
+
+ def count_sentinels() -> int:
+ """Count the number of CachedPropertySentinel instances in gc."""
+ gc.collect()
+ return sum(
+ 1 for obj in gc.get_objects() if isinstance(obj, CachedPropertySentinel)
+ )
+
+ class A:
+ def __init__(self) -> None:
+ """Init."""
+
+ @propcache_module.cached_property
+ def prop(self) -> CachedPropertySentinel:
+ """Return a sentinel object."""
+ return CachedPropertySentinel()
+
+ initial_sentinel_count = count_sentinels()
+
+ a = A()
+
+ # First access - creates and caches the object
+ result = a.prop
+ # sys.getrefcount returns 1 higher than actual (the temp ref from the call)
+ # After first access: result owns 1, __dict__ owns 1, getrefcount call owns 1 = 3
+ initial_refcount = sys.getrefcount(result)
+
+ # Should have exactly 1 Sentinel instance now
+ assert count_sentinels() == initial_sentinel_count + 1
+
+ # Second access - should return the cached object without creating new refs
+ result2 = a.prop
+ assert result is result2
+ # After second access: result owns 1, result2 owns 1, __dict__ owns 1, getrefcount call owns 1 = 4
+ # Only result2 should add 1
+ second_refcount = sys.getrefcount(result)
+ assert second_refcount == initial_refcount + 1
+
+ # Still should have exactly 1 Sentinel instance
+ assert count_sentinels() == initial_sentinel_count + 1
+
+ # Third access
+ result3 = a.prop
+ assert result is result3
+ # result2 and result3 each add 1
+ third_refcount = sys.getrefcount(result)
+ assert third_refcount == initial_refcount + 2
+
+ # Clean up local refs - should be back to just result and __dict__
+ del result2
+ del result3
+ after_cleanup_refcount = sys.getrefcount(result)
+ assert after_cleanup_refcount == initial_refcount
+
+ # Clear the cache and verify no leak when re-fetching
+ # After clearing: only result owns it
+ del a.__dict__["prop"]
+ cleared_refcount = sys.getrefcount(result)
+ assert cleared_refcount == initial_refcount - 1 # No longer in __dict__
+
+ # Re-fetch - this should create a new object, not affect old one
+ result4 = a.prop
+ assert result4 is not result # Should be a new object
+ refetch_refcount = sys.getrefcount(result)
+ assert refetch_refcount == cleared_refcount # Original object refcount unchanged
+
+ # Now we should have 2 Sentinel instances:
+ # - original in `result`
+ # - new one in `result4`
+ assert count_sentinels() == initial_sentinel_count + 2
diff --git a/contrib/python/propcache/tests/test_init.py b/contrib/python/propcache/tests/test_init.py
new file mode 100644
index 00000000000..6bcffe7faa6
--- /dev/null
+++ b/contrib/python/propcache/tests/test_init.py
@@ -0,0 +1,43 @@
+"""Test imports can happen from top-level."""
+
+import pytest
+
+import propcache
+from propcache import _helpers
+
+
+def test_api_at_top_level() -> None:
+ """Verify the public API is accessible at top-level."""
+ assert propcache.cached_property is not None
+ assert propcache.under_cached_property is not None
+ assert propcache.cached_property is _helpers.cached_property
+ assert propcache.under_cached_property is _helpers.under_cached_property
+
+
+ "prop_name",
+ ("cached_property", "under_cached_property"),
+)
+def test_public_api_is_discoverable_in_dir(prop_name: str) -> None:
+ """Verify the public API is discoverable programmatically."""
+ assert prop_name in dir(propcache)
+
+
+def test_importing_invalid_attr_raises() -> None:
+ """Verify importing an invalid attribute raises an AttributeError."""
+ match = r"^module 'propcache' has no attribute 'invalid_attr'$"
+ with pytest.raises(AttributeError, match=match):
+ propcache.invalid_attr
+
+
+def test_import_error_invalid_attr() -> None:
+ """Verify importing an invalid attribute raises an ImportError."""
+ # No match here because the error is raised by the import system
+ # and may vary between Python versions.
+ with pytest.raises(ImportError):
+ from propcache import invalid_attr # noqa: F401
+
+
+def test_no_wildcard_imports() -> None:
+ """Verify wildcard imports are prohibited."""
+ assert not propcache.__all__
diff --git a/contrib/python/propcache/tests/test_under_cached_property.py b/contrib/python/propcache/tests/test_under_cached_property.py
new file mode 100644
index 00000000000..9c0dc304f65
--- /dev/null
+++ b/contrib/python/propcache/tests/test_under_cached_property.py
@@ -0,0 +1,252 @@
+import gc
+import sys
+from collections.abc import Callable
+from typing import TYPE_CHECKING, Any, Protocol, TypedDict, TypeVar
+
+import pytest
+
+from propcache.api import under_cached_property
+
+IS_PYPY = hasattr(sys, "pypy_version_info")
+
+if sys.version_info >= (3, 11):
+ from typing import assert_type
+
+_T_co = TypeVar("_T_co", covariant=True)
+
+
+class APIProtocol(Protocol):
+ def under_cached_property(
+ self, func: Callable[[Any], _T_co]
+ ) -> under_cached_property[_T_co]: ...
+
+
+def test_under_cached_property(propcache_module: APIProtocol) -> None:
+ class A:
+ def __init__(self) -> None:
+ self._cache: dict[str, int] = {}
+
+ @propcache_module.under_cached_property
+ def prop(self) -> int:
+ return 1
+
+ @propcache_module.under_cached_property
+ def prop2(self) -> str:
+ return "foo"
+
+ a = A()
+ if sys.version_info >= (3, 11):
+ assert_type(a.prop, int)
+ assert a.prop == 1
+ if sys.version_info >= (3, 11):
+ assert_type(a.prop2, str)
+ assert a.prop2 == "foo"
+
+
+def test_under_cached_property_typeddict(propcache_module: APIProtocol) -> None:
+ """Test static typing passes with TypedDict."""
+
+ class _Cache(TypedDict, total=False):
+ prop: int
+ prop2: str
+
+ class A:
+ def __init__(self) -> None:
+ self._cache: _Cache = {}
+
+ @propcache_module.under_cached_property
+ def prop(self) -> int:
+ return 1
+
+ @propcache_module.under_cached_property
+ def prop2(self) -> str:
+ return "foo"
+
+ a = A()
+ if sys.version_info >= (3, 11):
+ assert_type(a.prop, int)
+ assert a.prop == 1
+ if sys.version_info >= (3, 11):
+ assert_type(a.prop2, str)
+ assert a.prop2 == "foo"
+
+
+def test_under_cached_property_assignment(propcache_module: APIProtocol) -> None:
+ class A:
+ def __init__(self) -> None:
+ self._cache: dict[str, Any] = {}
+
+ @propcache_module.under_cached_property
+ def prop(self) -> None:
+ """Mock property."""
+
+ a = A()
+
+ with pytest.raises(AttributeError):
+ a.prop = 123 # type: ignore[assignment]
+
+
+def test_under_cached_property_without_cache(propcache_module: APIProtocol) -> None:
+ class A:
+ def __init__(self) -> None:
+ """Init."""
+ self._cache: dict[str, int] = {}
+
+ @propcache_module.under_cached_property
+ def prop(self) -> None:
+ """Mock property."""
+
+ a = A()
+
+ with pytest.raises(AttributeError):
+ a.prop = 123 # type: ignore[assignment]
+
+
+def test_under_cached_property_check_without_cache(
+ propcache_module: APIProtocol,
+) -> None:
+ class A:
+ def __init__(self) -> None:
+ """Init."""
+ # Note that self._cache is intentionally missing
+ # here to verify AttributeError
+
+ @propcache_module.under_cached_property
+ def prop(self) -> None:
+ """Mock property."""
+
+ a = A()
+ with pytest.raises(AttributeError):
+ _ = a.prop # type: ignore[call-overload]
+
+
+def test_under_cached_property_caching(propcache_module: APIProtocol) -> None:
+ class A:
+ def __init__(self) -> None:
+ self._cache: dict[str, int] = {}
+
+ @propcache_module.under_cached_property
+ def prop(self) -> int:
+ """Docstring."""
+ return 1
+
+ a = A()
+ assert a.prop == 1
+
+
+def test_under_cached_property_class_docstring(propcache_module: APIProtocol) -> None:
+ class A:
+ def __init__(self) -> None:
+ """Init."""
+
+ @propcache_module.under_cached_property
+ def prop(self) -> None:
+ """Docstring."""
+
+ if TYPE_CHECKING:
+ # At type checking, the fixture doesn't represent the real module, so
+ # we use the global-level imported module to verify the isinstance() check here
+ # matches the behaviour users would see in real code.
+ assert isinstance(A.prop, under_cached_property)
+ else:
+ assert isinstance(A.prop, propcache_module.under_cached_property)
+ assert "Docstring." == A.prop.__doc__
+
+
+def test_ensured_wrapped_function_is_accessible(propcache_module: APIProtocol) -> None:
+ """Test that the wrapped function can be accessed from python."""
+
+ class A:
+ def __init__(self) -> None:
+ """Init."""
+ self._cache: dict[str, int] = {}
+
+ @propcache_module.under_cached_property
+ def prop(self) -> int:
+ """Docstring."""
+ return 1
+
+ a = A()
+ assert A.prop.wrapped(a) == 1
+
+
[email protected](IS_PYPY, reason="PyPy has no C extension")
+def test_under_cached_property_no_refcount_leak(propcache_module: APIProtocol) -> None:
+ """Test that under_cached_property does not leak references."""
+
+ class UnderCachedPropertySentinel:
+ """A unique object we can track."""
+
+ def count_sentinels() -> int:
+ """Count the number of UnderCachedPropertySentinel instances in gc."""
+ gc.collect()
+ return sum(
+ 1
+ for obj in gc.get_objects()
+ if isinstance(obj, UnderCachedPropertySentinel)
+ )
+
+ class A:
+ def __init__(self) -> None:
+ """Init."""
+ self._cache: dict[str, Any] = {}
+
+ @propcache_module.under_cached_property
+ def prop(self) -> UnderCachedPropertySentinel:
+ """Return a sentinel object."""
+ return UnderCachedPropertySentinel()
+
+ initial_sentinel_count = count_sentinels()
+
+ a = A()
+
+ # First access - creates and caches the object
+ result = a.prop
+ # sys.getrefcount returns 1 higher than actual (the temp ref from the call)
+ # After first access: result owns 1, _cache owns 1, getrefcount call owns 1 = 3
+ initial_refcount = sys.getrefcount(result)
+
+ # Should have exactly 1 Sentinel instance now
+ assert count_sentinels() == initial_sentinel_count + 1
+
+ # Second access - should return the cached object without creating new refs
+ result2 = a.prop
+ assert result is result2
+ # After second access: result owns 1, result2 owns 1, _cache owns 1, getrefcount call owns 1 = 4
+ # Only result2 should add 1
+ second_refcount = sys.getrefcount(result)
+ assert second_refcount == initial_refcount + 1
+
+ # Still should have exactly 1 Sentinel instance
+ assert count_sentinels() == initial_sentinel_count + 1
+
+ # Third access
+ result3 = a.prop
+ assert result is result3
+ # result2 and result3 each add 1
+ third_refcount = sys.getrefcount(result)
+ assert third_refcount == initial_refcount + 2
+
+ # Clean up local refs - should be back to just result and _cache
+ del result2
+ del result3
+ after_cleanup_refcount = sys.getrefcount(result)
+ assert after_cleanup_refcount == initial_refcount
+
+ # Clear the cache and verify no leak when re-fetching
+ # After clearing: only result owns it
+ del a._cache["prop"]
+ cleared_refcount = sys.getrefcount(result)
+ assert cleared_refcount == initial_refcount - 1 # No longer in _cache
+
+ # Re-fetch - this should create a new object, not affect old one
+ result4 = a.prop
+ assert result4 is not result # Should be a new object
+ refetch_refcount = sys.getrefcount(result)
+ assert refetch_refcount == cleared_refcount # Original object refcount unchanged
+
+ # Now we should have 2 Sentinel instances:
+ # - original in `result`
+ # - new one in `result4`
+ assert count_sentinels() == initial_sentinel_count + 2
diff --git a/contrib/python/propcache/tests/ya.make b/contrib/python/propcache/tests/ya.make
new file mode 100644
index 00000000000..53173b0a58f
--- /dev/null
+++ b/contrib/python/propcache/tests/ya.make
@@ -0,0 +1,11 @@
+PY3TEST()
+
+PEERDIR(
+ contrib/python/propcache
+)
+
+ALL_PYTEST_SRCS()
+
+NO_LINT()
+
+END()
diff --git a/contrib/python/propcache/ya.make b/contrib/python/propcache/ya.make
new file mode 100644
index 00000000000..dea81d7aa7f
--- /dev/null
+++ b/contrib/python/propcache/ya.make
@@ -0,0 +1,34 @@
+# Generated by devtools/yamaker (pypi).
+
+PY3_LIBRARY()
+
+VERSION(0.5.2)
+
+LICENSE(Apache-2.0)
+
+NO_COMPILER_WARNINGS()
+
+NO_LINT()
+
+PY_SRCS(
+ TOP_LEVEL
+ propcache/__init__.py
+ propcache/_helpers.py
+ propcache/_helpers_py.py
+ propcache/api.py
+ CYTHON_C
+ propcache/_helpers_c.pyx
+)
+
+RESOURCE_FILES(
+ PREFIX contrib/python/propcache/
+ .dist-info/METADATA
+ .dist-info/top_level.txt
+ propcache/py.typed
+)
+
+END()
+
+RECURSE_FOR_TESTS(
+ tests
+)