diff options
| author | Maxim Yurchuk <[email protected]> | 2024-08-14 21:06:39 +0300 |
|---|---|---|
| committer | GitHub <[email protected]> | 2024-08-14 21:06:39 +0300 |
| commit | 207f84602fdcce806fc2b6f972cd4e7e7968a882 (patch) | |
| tree | 31382ec1e683530d0fddf4a9a69ae4c318305421 /contrib/python | |
| parent | d1f17fc6e4c6a825c1b3809af989e6bacda6ad8d (diff) | |
| parent | 88460fbe6a25478b8144261e2918a4713dd7dd2d (diff) | |
Merge pull request #7538 from ydb-platform/mergelibs-240807-1311
Library import 240807-1311
Diffstat (limited to 'contrib/python')
36 files changed, 1035 insertions, 67 deletions
diff --git a/contrib/python/certifi/py2/ya.make b/contrib/python/certifi/py2/ya.make index 274da44efc0..27aa0018f46 100644 --- a/contrib/python/certifi/py2/ya.make +++ b/contrib/python/certifi/py2/ya.make @@ -2,6 +2,8 @@ PY2_LIBRARY() LICENSE(Service-Py23-Proxy) +VERSION(Service-no-external-sources) + PEERDIR( library/python/certifi ) diff --git a/contrib/python/certifi/py3/ya.make b/contrib/python/certifi/py3/ya.make index fa3aa2c9d01..e841bd82d54 100644 --- a/contrib/python/certifi/py3/ya.make +++ b/contrib/python/certifi/py3/ya.make @@ -2,6 +2,8 @@ PY3_LIBRARY() LICENSE(Service-Py23-Proxy) +VERSION(Service-no-external-sources) + PEERDIR( library/python/certifi ) diff --git a/contrib/python/hypothesis/py3/.dist-info/METADATA b/contrib/python/hypothesis/py3/.dist-info/METADATA index ffee48a82f0..223aafcb694 100644 --- a/contrib/python/hypothesis/py3/.dist-info/METADATA +++ b/contrib/python/hypothesis/py3/.dist-info/METADATA @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: hypothesis -Version: 6.108.2 +Version: 6.108.4 Summary: A library for property-based testing Home-page: https://hypothesis.works Author: David R. MacIver and Zac Hatfield-Dodds @@ -41,10 +41,10 @@ Requires-Dist: exceptiongroup >=1.0.0 ; python_version < "3.11" Provides-Extra: all Requires-Dist: black >=19.10b0 ; extra == 'all' Requires-Dist: click >=7.0 ; extra == 'all' -Requires-Dist: crosshair-tool >=0.0.61 ; extra == 'all' +Requires-Dist: crosshair-tool >=0.0.63 ; extra == 'all' Requires-Dist: django >=3.2 ; extra == 'all' Requires-Dist: dpcontracts >=0.4 ; extra == 'all' -Requires-Dist: hypothesis-crosshair >=0.0.7 ; extra == 'all' +Requires-Dist: hypothesis-crosshair >=0.0.9 ; extra == 'all' Requires-Dist: lark >=0.10.1 ; extra == 'all' Requires-Dist: libcst >=0.3.16 ; extra == 'all' Requires-Dist: numpy >=1.17.3 ; extra == 'all' @@ -63,8 +63,8 @@ Requires-Dist: rich >=9.0.0 ; extra == 'cli' Provides-Extra: codemods Requires-Dist: libcst >=0.3.16 ; extra == 'codemods' Provides-Extra: crosshair -Requires-Dist: hypothesis-crosshair >=0.0.7 ; extra == 'crosshair' -Requires-Dist: crosshair-tool >=0.0.61 ; extra == 'crosshair' +Requires-Dist: hypothesis-crosshair >=0.0.9 ; extra == 'crosshair' +Requires-Dist: crosshair-tool >=0.0.63 ; extra == 'crosshair' Provides-Extra: dateutil Requires-Dist: python-dateutil >=1.4 ; extra == 'dateutil' Provides-Extra: django diff --git a/contrib/python/hypothesis/py3/_hypothesis_pytestplugin.py b/contrib/python/hypothesis/py3/_hypothesis_pytestplugin.py index e82b528bb53..9df7817c68c 100644 --- a/contrib/python/hypothesis/py3/_hypothesis_pytestplugin.py +++ b/contrib/python/hypothesis/py3/_hypothesis_pytestplugin.py @@ -309,7 +309,7 @@ else: with current_pytest_item.with_value(item): yield if store.results: - item.hypothesis_report_information = list(store.results) + item.hypothesis_report_information = "\n".join(store.results) def _stash_get(config, key, default): if hasattr(config, "stash"): @@ -325,9 +325,7 @@ else: def pytest_runtest_makereport(item, call): report = (yield).get_result() if hasattr(item, "hypothesis_report_information"): - report.sections.append( - ("Hypothesis", "\n".join(item.hypothesis_report_information)) - ) + report.sections.append(("Hypothesis", item.hypothesis_report_information)) if report.when != "teardown": return diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/junkdrawer.py b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/junkdrawer.py index 7dbab5b9717..4465f59e5c4 100644 --- a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/junkdrawer.py +++ b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/junkdrawer.py @@ -421,6 +421,10 @@ _gc_initialized = False _gc_start = 0 _gc_cumulative_time = 0 +# Since gc_callback potentially runs in test context, and perf_counter +# might be monkeypatched, we store a reference to the real one. +_perf_counter = time.perf_counter + def gc_cumulative_time() -> float: global _gc_initialized @@ -430,7 +434,7 @@ def gc_cumulative_time() -> float: def gc_callback(phase, info): global _gc_start, _gc_cumulative_time try: - now = time.perf_counter() + now = _perf_counter() if phase == "start": _gc_start = now elif phase == "stop" and _gc_start > 0: diff --git a/contrib/python/hypothesis/py3/hypothesis/reporting.py b/contrib/python/hypothesis/py3/hypothesis/reporting.py index a0f300b2bcb..19073c5aff2 100644 --- a/contrib/python/hypothesis/py3/hypothesis/reporting.py +++ b/contrib/python/hypothesis/py3/hypothesis/reporting.py @@ -8,8 +8,6 @@ # v. 2.0. If a copy of the MPL was not distributed with this file, You can # obtain one at https://mozilla.org/MPL/2.0/. -import inspect - from hypothesis._settings import Verbosity, settings from hypothesis.internal.compat import escape_unicode_characters from hypothesis.utils.dynamicvariables import DynamicVariable @@ -37,14 +35,6 @@ def current_verbosity(): return settings.default.verbosity -def to_text(textish): - if inspect.isfunction(textish): - textish = textish() - if isinstance(textish, bytes): - textish = textish.decode() - return textish - - def verbose_report(text): if current_verbosity() >= Verbosity.verbose: base_report(text) @@ -61,4 +51,5 @@ def report(text): def base_report(text): - current_reporter()(to_text(text)) + assert isinstance(text, str), f"unexpected non-str {text=}" + current_reporter()(text) diff --git a/contrib/python/hypothesis/py3/hypothesis/version.py b/contrib/python/hypothesis/py3/hypothesis/version.py index 4858f7fb0ca..29e2144b92a 100644 --- a/contrib/python/hypothesis/py3/hypothesis/version.py +++ b/contrib/python/hypothesis/py3/hypothesis/version.py @@ -8,5 +8,5 @@ # v. 2.0. If a copy of the MPL was not distributed with this file, You can # obtain one at https://mozilla.org/MPL/2.0/. -__version_info__ = (6, 108, 2) +__version_info__ = (6, 108, 4) __version__ = ".".join(map(str, __version_info__)) diff --git a/contrib/python/hypothesis/py3/ya.make b/contrib/python/hypothesis/py3/ya.make index 63eb354c8e2..4efa89329dd 100644 --- a/contrib/python/hypothesis/py3/ya.make +++ b/contrib/python/hypothesis/py3/ya.make @@ -2,7 +2,7 @@ PY3_LIBRARY() -VERSION(6.108.2) +VERSION(6.108.4) LICENSE(MPL-2.0) diff --git a/contrib/python/importlib-metadata/py3/.dist-info/METADATA b/contrib/python/importlib-metadata/py3/.dist-info/METADATA index d7dd4a31a2e..691fb545bff 100644 --- a/contrib/python/importlib-metadata/py3/.dist-info/METADATA +++ b/contrib/python/importlib-metadata/py3/.dist-info/METADATA @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: importlib_metadata -Version: 8.0.0 +Version: 8.1.0 Summary: Read metadata from Python packages Author-email: "Jason R. Coombs" <[email protected]> Project-URL: Source, https://github.com/python/importlib_metadata @@ -28,13 +28,13 @@ Requires-Dist: pytest-checkdocs >=2.4 ; extra == 'test' Requires-Dist: pytest-cov ; extra == 'test' Requires-Dist: pytest-mypy ; extra == 'test' Requires-Dist: pytest-enabler >=2.2 ; extra == 'test' -Requires-Dist: pytest-ruff >=0.2.1 ; extra == 'test' Requires-Dist: packaging ; extra == 'test' Requires-Dist: pyfakefs ; extra == 'test' Requires-Dist: flufl.flake8 ; extra == 'test' Requires-Dist: pytest-perf >=0.9.2 ; extra == 'test' Requires-Dist: jaraco.test >=5.4 ; extra == 'test' Requires-Dist: importlib-resources >=1.3 ; (python_version < "3.9") and extra == 'test' +Requires-Dist: pytest-ruff >=0.2.1 ; (sys_platform != "cygwin") and extra == 'test' .. image:: https://img.shields.io/pypi/v/importlib_metadata.svg :target: https://pypi.org/project/importlib_metadata diff --git a/contrib/python/importlib-metadata/py3/importlib_metadata/__init__.py b/contrib/python/importlib-metadata/py3/importlib_metadata/__init__.py index 6147ca9f5e7..c9730079926 100644 --- a/contrib/python/importlib-metadata/py3/importlib_metadata/__init__.py +++ b/contrib/python/importlib-metadata/py3/importlib_metadata/__init__.py @@ -24,7 +24,7 @@ from ._compat import ( install, ) from ._functools import method_cache, pass_none -from ._itertools import always_iterable, unique_everseen +from ._itertools import always_iterable, bucket, unique_everseen from ._meta import PackageMetadata, SimplePath from contextlib import suppress @@ -393,7 +393,7 @@ class Distribution(metaclass=abc.ABCMeta): if not name: raise ValueError("A distribution name is required.") try: - return next(iter(cls.discover(name=name))) + return next(iter(cls._prefer_valid(cls.discover(name=name)))) except StopIteration: raise PackageNotFoundError(name) @@ -418,6 +418,16 @@ class Distribution(metaclass=abc.ABCMeta): ) @staticmethod + def _prefer_valid(dists: Iterable[Distribution]) -> Iterable[Distribution]: + """ + Prefer (move to the front) distributions that have metadata. + + Ref python/importlib_resources#489. + """ + buckets = bucket(dists, lambda dist: bool(dist.metadata)) + return itertools.chain(buckets[True], buckets[False]) + + @staticmethod def at(path: str | os.PathLike[str]) -> Distribution: """Return a Distribution for the indicated metadata path. diff --git a/contrib/python/importlib-metadata/py3/importlib_metadata/_itertools.py b/contrib/python/importlib-metadata/py3/importlib_metadata/_itertools.py index d4ca9b9140e..79d37198ce7 100644 --- a/contrib/python/importlib-metadata/py3/importlib_metadata/_itertools.py +++ b/contrib/python/importlib-metadata/py3/importlib_metadata/_itertools.py @@ -1,3 +1,4 @@ +from collections import defaultdict, deque from itertools import filterfalse @@ -71,3 +72,100 @@ def always_iterable(obj, base_type=(str, bytes)): return iter(obj) except TypeError: return iter((obj,)) + + +# Copied from more_itertools 10.3 +class bucket: + """Wrap *iterable* and return an object that buckets the iterable into + child iterables based on a *key* function. + + >>> iterable = ['a1', 'b1', 'c1', 'a2', 'b2', 'c2', 'b3'] + >>> s = bucket(iterable, key=lambda x: x[0]) # Bucket by 1st character + >>> sorted(list(s)) # Get the keys + ['a', 'b', 'c'] + >>> a_iterable = s['a'] + >>> next(a_iterable) + 'a1' + >>> next(a_iterable) + 'a2' + >>> list(s['b']) + ['b1', 'b2', 'b3'] + + The original iterable will be advanced and its items will be cached until + they are used by the child iterables. This may require significant storage. + + By default, attempting to select a bucket to which no items belong will + exhaust the iterable and cache all values. + If you specify a *validator* function, selected buckets will instead be + checked against it. + + >>> from itertools import count + >>> it = count(1, 2) # Infinite sequence of odd numbers + >>> key = lambda x: x % 10 # Bucket by last digit + >>> validator = lambda x: x in {1, 3, 5, 7, 9} # Odd digits only + >>> s = bucket(it, key=key, validator=validator) + >>> 2 in s + False + >>> list(s[2]) + [] + + """ + + def __init__(self, iterable, key, validator=None): + self._it = iter(iterable) + self._key = key + self._cache = defaultdict(deque) + self._validator = validator or (lambda x: True) + + def __contains__(self, value): + if not self._validator(value): + return False + + try: + item = next(self[value]) + except StopIteration: + return False + else: + self._cache[value].appendleft(item) + + return True + + def _get_values(self, value): + """ + Helper to yield items from the parent iterator that match *value*. + Items that don't match are stored in the local cache as they + are encountered. + """ + while True: + # If we've cached some items that match the target value, emit + # the first one and evict it from the cache. + if self._cache[value]: + yield self._cache[value].popleft() + # Otherwise we need to advance the parent iterator to search for + # a matching item, caching the rest. + else: + while True: + try: + item = next(self._it) + except StopIteration: + return + item_value = self._key(item) + if item_value == value: + yield item + break + elif self._validator(item_value): + self._cache[item_value].append(item) + + def __iter__(self): + for item in self._it: + item_value = self._key(item) + if self._validator(item_value): + self._cache[item_value].append(item) + + yield from self._cache.keys() + + def __getitem__(self, value): + if not self._validator(value): + return iter(()) + + return self._get_values(value) diff --git a/contrib/python/importlib-metadata/py3/ya.make b/contrib/python/importlib-metadata/py3/ya.make index ad2a140f18c..8e7b3102b36 100644 --- a/contrib/python/importlib-metadata/py3/ya.make +++ b/contrib/python/importlib-metadata/py3/ya.make @@ -2,7 +2,7 @@ PY3_LIBRARY() -VERSION(8.0.0) +VERSION(8.1.0) LICENSE(Apache-2.0) diff --git a/contrib/python/importlib-resources/ya.make b/contrib/python/importlib-resources/ya.make index 21d607abef0..c7ba0d70fcb 100644 --- a/contrib/python/importlib-resources/ya.make +++ b/contrib/python/importlib-resources/ya.make @@ -2,6 +2,9 @@ PY23_LIBRARY() LICENSE(Apache-2.0) +# The code is not taken from upstream. An analogue of version 1.0.2 has been implemented. See CONTRIB-1203 +VERSION(1.0.2) + PY_SRCS( TOP_LEVEL importlib_resources/__init__.py diff --git a/contrib/python/mergedeep/.dist-info/METADATA b/contrib/python/mergedeep/.dist-info/METADATA new file mode 100644 index 00000000000..23452d786be --- /dev/null +++ b/contrib/python/mergedeep/.dist-info/METADATA @@ -0,0 +1,154 @@ +Metadata-Version: 2.1 +Name: mergedeep +Version: 1.3.4 +Summary: A deep merge function for 🐍. +Home-page: https://github.com/clarketm/mergedeep +Author: Travis Clarke +Author-email: [email protected] +License: UNKNOWN +Platform: UNKNOWN +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: OS Independent +Requires-Python: >=3.6 +Description-Content-Type: text/markdown + +# [mergedeep](https://mergedeep.readthedocs.io/en/latest/) + +[](https://pypi.org/project/mergedeep/) +[](https://pypi.org/project/mergedeep/) +[](https://pepy.tech/project/mergedeep) +[](https://anaconda.org/conda-forge/mergedeep) +[](https://anaconda.org/conda-forge/mergedeep) +[](https://mergedeep.readthedocs.io/en/latest/?badge=latest) + +A deep merge function for 🐍. + +[Check out the mergedeep docs](https://mergedeep.readthedocs.io/en/latest/) + +## Installation + +```bash +$ pip install mergedeep +``` + +## Usage + +```text +merge(destination: MutableMapping, *sources: Mapping, strategy: Strategy = Strategy.REPLACE) -> MutableMapping +``` + +Deep merge without mutating the source dicts. + +```python3 +from mergedeep import merge + +a = {"keyA": 1} +b = {"keyB": {"sub1": 10}} +c = {"keyB": {"sub2": 20}} + +merged = merge({}, a, b, c) + +print(merged) +# {"keyA": 1, "keyB": {"sub1": 10, "sub2": 20}} +``` + +Deep merge into an existing dict. +```python3 +from mergedeep import merge + +a = {"keyA": 1} +b = {"keyB": {"sub1": 10}} +c = {"keyB": {"sub2": 20}} + +merge(a, b, c) + +print(a) +# {"keyA": 1, "keyB": {"sub1": 10, "sub2": 20}} +``` + +### Merge strategies: + +1. Replace (*default*) + +> `Strategy.REPLACE` + +```python3 +# When `destination` and `source` keys are the same, replace the `destination` value with one from `source` (default). + +# Note: with multiple sources, the `last` (i.e. rightmost) source value will be what appears in the merged result. + +from mergedeep import merge, Strategy + +dst = {"key": [1, 2]} +src = {"key": [3, 4]} + +merge(dst, src, strategy=Strategy.REPLACE) +# same as: merge(dst, src) + +print(dst) +# {"key": [3, 4]} +``` + +2. Additive + +> `Strategy.ADDITIVE` + +```python3 +# When `destination` and `source` values are both the same additive collection type, extend `destination` by adding values from `source`. +# Additive collection types include: `list`, `tuple`, `set`, and `Counter` + +# Note: if the values are not additive collections of the same type, then fallback to a `REPLACE` merge. + +from mergedeep import merge, Strategy + +dst = {"key": [1, 2], "count": Counter({"a": 1, "b": 1})} +src = {"key": [3, 4], "count": Counter({"a": 1, "c": 1})} + +merge(dst, src, strategy=Strategy.ADDITIVE) + +print(dst) +# {"key": [1, 2, 3, 4], "count": Counter({"a": 2, "b": 1, "c": 1})} +``` + +3. Typesafe replace + +> `Strategy.TYPESAFE_REPLACE` or `Strategy.TYPESAFE` + +```python3 +# When `destination` and `source` values are of different types, raise `TypeError`. Otherwise, perform a `REPLACE` merge. + +from mergedeep import merge, Strategy + +dst = {"key": [1, 2]} +src = {"key": {3, 4}} + +merge(dst, src, strategy=Strategy.TYPESAFE_REPLACE) # same as: `Strategy.TYPESAFE` +# TypeError: destination type: <class 'list'> differs from source type: <class 'set'> for key: "key" +``` + +4. Typesafe additive + +> `Strategy.TYPESAFE_ADDITIVE` + +```python3 +# When `destination` and `source` values are of different types, raise `TypeError`. Otherwise, perform a `ADDITIVE` merge. + +from mergedeep import merge, Strategy + +dst = {"key": [1, 2]} +src = {"key": {3, 4}} + +merge(dst, src, strategy=Strategy.TYPESAFE_ADDITIVE) +# TypeError: destination type: <class 'list'> differs from source type: <class 'set'> for key: "key" +``` + +## License + +MIT © [**Travis Clarke**](https://blog.travismclarke.com/) + + diff --git a/contrib/python/mergedeep/.dist-info/top_level.txt b/contrib/python/mergedeep/.dist-info/top_level.txt new file mode 100644 index 00000000000..5413932b268 --- /dev/null +++ b/contrib/python/mergedeep/.dist-info/top_level.txt @@ -0,0 +1 @@ +mergedeep diff --git a/contrib/python/mergedeep/.yandex_meta/yamaker.yaml b/contrib/python/mergedeep/.yandex_meta/yamaker.yaml new file mode 100644 index 00000000000..b88229ef483 --- /dev/null +++ b/contrib/python/mergedeep/.yandex_meta/yamaker.yaml @@ -0,0 +1,2 @@ +mark_as_tests: + - mergedeep/test_mergedeep.py diff --git a/contrib/python/mergedeep/LICENSE b/contrib/python/mergedeep/LICENSE new file mode 100644 index 00000000000..45c64408162 --- /dev/null +++ b/contrib/python/mergedeep/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2019 Travis Clarke <[email protected]> (https://www.travismclarke.com/) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/contrib/python/mergedeep/README.md b/contrib/python/mergedeep/README.md new file mode 100644 index 00000000000..86322730cd9 --- /dev/null +++ b/contrib/python/mergedeep/README.md @@ -0,0 +1,133 @@ +# [mergedeep](https://mergedeep.readthedocs.io/en/latest/) + +[](https://pypi.org/project/mergedeep/) +[](https://pypi.org/project/mergedeep/) +[](https://pepy.tech/project/mergedeep) +[](https://anaconda.org/conda-forge/mergedeep) +[](https://anaconda.org/conda-forge/mergedeep) +[](https://mergedeep.readthedocs.io/en/latest/?badge=latest) + +A deep merge function for 🐍. + +[Check out the mergedeep docs](https://mergedeep.readthedocs.io/en/latest/) + +## Installation + +```bash +$ pip install mergedeep +``` + +## Usage + +```text +merge(destination: MutableMapping, *sources: Mapping, strategy: Strategy = Strategy.REPLACE) -> MutableMapping +``` + +Deep merge without mutating the source dicts. + +```python3 +from mergedeep import merge + +a = {"keyA": 1} +b = {"keyB": {"sub1": 10}} +c = {"keyB": {"sub2": 20}} + +merged = merge({}, a, b, c) + +print(merged) +# {"keyA": 1, "keyB": {"sub1": 10, "sub2": 20}} +``` + +Deep merge into an existing dict. +```python3 +from mergedeep import merge + +a = {"keyA": 1} +b = {"keyB": {"sub1": 10}} +c = {"keyB": {"sub2": 20}} + +merge(a, b, c) + +print(a) +# {"keyA": 1, "keyB": {"sub1": 10, "sub2": 20}} +``` + +### Merge strategies: + +1. Replace (*default*) + +> `Strategy.REPLACE` + +```python3 +# When `destination` and `source` keys are the same, replace the `destination` value with one from `source` (default). + +# Note: with multiple sources, the `last` (i.e. rightmost) source value will be what appears in the merged result. + +from mergedeep import merge, Strategy + +dst = {"key": [1, 2]} +src = {"key": [3, 4]} + +merge(dst, src, strategy=Strategy.REPLACE) +# same as: merge(dst, src) + +print(dst) +# {"key": [3, 4]} +``` + +2. Additive + +> `Strategy.ADDITIVE` + +```python3 +# When `destination` and `source` values are both the same additive collection type, extend `destination` by adding values from `source`. +# Additive collection types include: `list`, `tuple`, `set`, and `Counter` + +# Note: if the values are not additive collections of the same type, then fallback to a `REPLACE` merge. + +from mergedeep import merge, Strategy + +dst = {"key": [1, 2], "count": Counter({"a": 1, "b": 1})} +src = {"key": [3, 4], "count": Counter({"a": 1, "c": 1})} + +merge(dst, src, strategy=Strategy.ADDITIVE) + +print(dst) +# {"key": [1, 2, 3, 4], "count": Counter({"a": 2, "b": 1, "c": 1})} +``` + +3. Typesafe replace + +> `Strategy.TYPESAFE_REPLACE` or `Strategy.TYPESAFE` + +```python3 +# When `destination` and `source` values are of different types, raise `TypeError`. Otherwise, perform a `REPLACE` merge. + +from mergedeep import merge, Strategy + +dst = {"key": [1, 2]} +src = {"key": {3, 4}} + +merge(dst, src, strategy=Strategy.TYPESAFE_REPLACE) # same as: `Strategy.TYPESAFE` +# TypeError: destination type: <class 'list'> differs from source type: <class 'set'> for key: "key" +``` + +4. Typesafe additive + +> `Strategy.TYPESAFE_ADDITIVE` + +```python3 +# When `destination` and `source` values are of different types, raise `TypeError`. Otherwise, perform a `ADDITIVE` merge. + +from mergedeep import merge, Strategy + +dst = {"key": [1, 2]} +src = {"key": {3, 4}} + +merge(dst, src, strategy=Strategy.TYPESAFE_ADDITIVE) +# TypeError: destination type: <class 'list'> differs from source type: <class 'set'> for key: "key" +``` + +## License + +MIT © [**Travis Clarke**](https://blog.travismclarke.com/) diff --git a/contrib/python/mergedeep/mergedeep/__init__.py b/contrib/python/mergedeep/mergedeep/__init__.py new file mode 100644 index 00000000000..92d12f6c949 --- /dev/null +++ b/contrib/python/mergedeep/mergedeep/__init__.py @@ -0,0 +1,5 @@ +__version__ = "1.3.4" + +from mergedeep.mergedeep import merge, Strategy + +__all__ = ["merge", "Strategy"] diff --git a/contrib/python/mergedeep/mergedeep/mergedeep.py b/contrib/python/mergedeep/mergedeep/mergedeep.py new file mode 100644 index 00000000000..6dda8e82f38 --- /dev/null +++ b/contrib/python/mergedeep/mergedeep/mergedeep.py @@ -0,0 +1,100 @@ +from collections import Counter +from collections.abc import Mapping +from copy import deepcopy +from enum import Enum +from functools import reduce, partial +from typing import MutableMapping + + +class Strategy(Enum): + # Replace `destination` item with one from `source` (default). + REPLACE = 0 + # Combine `list`, `tuple`, `set`, or `Counter` types into one collection. + ADDITIVE = 1 + # Alias to: `TYPESAFE_REPLACE` + TYPESAFE = 2 + # Raise `TypeError` when `destination` and `source` types differ. Otherwise, perform a `REPLACE` merge. + TYPESAFE_REPLACE = 3 + # Raise `TypeError` when `destination` and `source` types differ. Otherwise, perform a `ADDITIVE` merge. + TYPESAFE_ADDITIVE = 4 + + +def _handle_merge_replace(destination, source, key): + if isinstance(destination[key], Counter) and isinstance(source[key], Counter): + # Merge both destination and source `Counter` as if they were a standard dict. + _deepmerge(destination[key], source[key]) + else: + # If a key exists in both objects and the values are `different`, the value from the `source` object will be used. + destination[key] = deepcopy(source[key]) + + +def _handle_merge_additive(destination, source, key): + # Values are combined into one long collection. + if isinstance(destination[key], list) and isinstance(source[key], list): + # Extend destination if both destination and source are `list` type. + destination[key].extend(deepcopy(source[key])) + elif isinstance(destination[key], set) and isinstance(source[key], set): + # Update destination if both destination and source are `set` type. + destination[key].update(deepcopy(source[key])) + elif isinstance(destination[key], tuple) and isinstance(source[key], tuple): + # Update destination if both destination and source are `tuple` type. + destination[key] = destination[key] + deepcopy(source[key]) + elif isinstance(destination[key], Counter) and isinstance(source[key], Counter): + # Update destination if both destination and source are `Counter` type. + destination[key].update(deepcopy(source[key])) + else: + _handle_merge[Strategy.REPLACE](destination, source, key) + + +def _handle_merge_typesafe(destination, source, key, strategy): + # Raise a TypeError if the destination and source types differ. + if type(destination[key]) is not type(source[key]): + raise TypeError( + f'destination type: {type(destination[key])} differs from source type: {type(source[key])} for key: "{key}"' + ) + else: + _handle_merge[strategy](destination, source, key) + + +_handle_merge = { + Strategy.REPLACE: _handle_merge_replace, + Strategy.ADDITIVE: _handle_merge_additive, + Strategy.TYPESAFE: partial(_handle_merge_typesafe, strategy=Strategy.REPLACE), + Strategy.TYPESAFE_REPLACE: partial(_handle_merge_typesafe, strategy=Strategy.REPLACE), + Strategy.TYPESAFE_ADDITIVE: partial(_handle_merge_typesafe, strategy=Strategy.ADDITIVE), +} + + +def _is_recursive_merge(a, b): + both_mapping = isinstance(a, Mapping) and isinstance(b, Mapping) + both_counter = isinstance(a, Counter) and isinstance(b, Counter) + return both_mapping and not both_counter + + +def _deepmerge(dst, src, strategy=Strategy.REPLACE): + for key in src: + if key in dst: + if _is_recursive_merge(dst[key], src[key]): + # If the key for both `dst` and `src` are both Mapping types (e.g. dict), then recurse. + _deepmerge(dst[key], src[key], strategy) + elif dst[key] is src[key]: + # If a key exists in both objects and the values are `same`, the value from the `dst` object will be used. + pass + else: + _handle_merge.get(strategy)(dst, src, key) + else: + # If the key exists only in `src`, the value from the `src` object will be used. + dst[key] = deepcopy(src[key]) + return dst + + +def merge(destination: MutableMapping, *sources: Mapping, strategy: Strategy = Strategy.REPLACE) -> MutableMapping: + """ + A deep merge function for 🐍. + + :param destination: The destination mapping. + :param sources: The source mappings. + :param strategy: The merge strategy. + :return: + """ + return reduce(partial(_deepmerge, strategy=strategy), sources, destination) diff --git a/contrib/python/mergedeep/mergedeep/test_mergedeep.py b/contrib/python/mergedeep/mergedeep/test_mergedeep.py new file mode 100644 index 00000000000..ef397288350 --- /dev/null +++ b/contrib/python/mergedeep/mergedeep/test_mergedeep.py @@ -0,0 +1,397 @@ +"""mergedeep test module""" +import inspect +import unittest +from collections import Counter +from copy import deepcopy + +from mergedeep import merge, Strategy + + +class test_mergedeep(unittest.TestCase): + """mergedeep function tests.""" + + ############################################################################################################################## + # REPLACE + ############################################################################################################################## + + def test_should_merge_3_dicts_into_new_dict_using_replace_strategy_and_only_mutate_target(self,): + expected = { + "a": {"b": {"c": 5, "_c": 15}, "B": {"C": 10}}, + "d": 3, + "e": {1: 2, "a": {"f": 2}}, + "f": [4, 5, 6], + "g": (100, 200), + "h": Counter({"a": 5, "b": 1, "c": 1}), + "i": 2, + "j": Counter({"z": 2}), + "z": Counter({"a": 2}), + } + + a = { + "a": {"b": {"c": 5}}, + "d": 1, + "e": {2: 3}, + "f": [1, 2, 3], + "g": (2, 4, 6), + "h": Counter({"a": 1, "b": 1}), + "j": 1, + } + a_copy = deepcopy(a) + + b = { + "a": {"B": {"C": 10}}, + "d": 2, + "e": 2, + "f": [4, 5, 6], + "g": (100, 200), + "h": Counter({"a": 5, "c": 1}), + "i": Counter({"a": 1}), + "z": Counter({"a": 2}), + } + b_copy = deepcopy(b) + + c = { + "a": {"b": {"_c": 15}}, + "d": 3, + "e": {1: 2, "a": {"f": 2}}, + "i": 2, + "j": Counter({"z": 2}), + "z": Counter({"a": 2}), + } + c_copy = deepcopy(c) + + actual = merge({}, a, b, c, strategy=Strategy.REPLACE) + + self.assertEqual(actual, expected) + self.assertEqual(a, a_copy) + self.assertEqual(b, b_copy) + self.assertEqual(c, c_copy) + + def test_should_merge_2_dicts_into_existing_dict_using_replace_strategy_and_only_mutate_target(self,): + expected = { + "a": {"b": {"c": 5, "_c": 15}, "B": {"C": 10}}, + "d": 3, + "e": {1: 2, "a": {"f": 2}}, + "f": [4, 5, 6], + "g": (100, 200), + "h": Counter({"a": 1, "b": 1, "c": 1}), + "i": 2, + "j": Counter({"z": 2}), + } + + a = { + "a": {"b": {"c": 5}}, + "d": 1, + "e": {2: 3}, + "f": [1, 2, 3], + "g": (2, 4, 6), + "h": Counter({"a": 1, "b": 1}), + "j": 1, + } + a_copy = deepcopy(a) + + b = { + "a": {"B": {"C": 10}}, + "d": 2, + "e": 2, + "f": [4, 5, 6], + "g": (100, 200), + "h": Counter({"a": 1, "c": 1}), + "i": Counter({"a": 1}), + } + b_copy = deepcopy(b) + + c = {"a": {"b": {"_c": 15}}, "d": 3, "e": {1: 2, "a": {"f": 2}}, "i": 2, "j": Counter({"z": 2})} + c_copy = deepcopy(c) + + actual = merge(a, b, c, strategy=Strategy.REPLACE) + + self.assertEqual(actual, expected) + self.assertEqual(actual, a) + self.assertNotEqual(a, a_copy) + self.assertEqual(b, b_copy) + self.assertEqual(c, c_copy) + + def test_should_have_default_strategy_of_replace(self): + func_spec = inspect.getfullargspec(merge) + default_strategy = Strategy.REPLACE + + self.assertEqual(func_spec.kwonlydefaults.get("strategy"), default_strategy) + + # mock_merge.method.assert_called_with(target, source, strategy=Strategy.REPLACE) + + ############################################################################################################################## + # ADDITIVE + ############################################################################################################################## + + def test_should_merge_3_dicts_into_new_dict_using_additive_strategy_on_lists_and_only_mutate_target(self,): + expected = { + "a": {"b": {"c": 5, "_c": 15}, "B": {"C": 10}}, + "d": 3, + "e": {1: 2, "a": {"f": 2}}, + "f": [1, 2, 3, 4, 5, 6], + } + + a = {"a": {"b": {"c": 5}}, "d": 1, "e": {2: 3}, "f": [1, 2, 3]} + a_copy = deepcopy(a) + + b = {"a": {"B": {"C": 10}}, "d": 2, "e": 2, "f": [4, 5, 6]} + b_copy = deepcopy(b) + + c = {"a": {"b": {"_c": 15}}, "d": 3, "e": {1: 2, "a": {"f": 2}}} + c_copy = deepcopy(c) + + actual = merge({}, a, b, c, strategy=Strategy.ADDITIVE) + + self.assertEqual(actual, expected) + self.assertEqual(a, a_copy) + self.assertEqual(b, b_copy) + self.assertEqual(c, c_copy) + + def test_should_merge_3_dicts_into_new_dict_using_additive_strategy_on_sets_and_only_mutate_target(self,): + expected = { + "a": {"b": {"c": 5, "_c": 15}, "B": {"C": 10}}, + "d": 3, + "e": {1: 2, "a": {"f": 2}}, + "f": {1, 2, 3, 4, 5, 6}, + } + + a = {"a": {"b": {"c": 5}}, "d": 1, "e": {2: 3}, "f": {1, 2, 3}} + a_copy = deepcopy(a) + + b = {"a": {"B": {"C": 10}}, "d": 2, "e": 2, "f": {4, 5, 6}} + b_copy = deepcopy(b) + + c = {"a": {"b": {"_c": 15}}, "d": 3, "e": {1: 2, "a": {"f": 2}}} + c_copy = deepcopy(c) + + actual = merge({}, a, b, c, strategy=Strategy.ADDITIVE) + + self.assertEqual(actual, expected) + self.assertEqual(a, a_copy) + self.assertEqual(b, b_copy) + self.assertEqual(c, c_copy) + + def test_should_merge_3_dicts_into_new_dict_using_additive_strategy_on_tuples_and_only_mutate_target(self,): + expected = { + "a": {"b": {"c": 5, "_c": 15}, "B": {"C": 10}}, + "d": 3, + "e": {1: 2, "a": {"f": 2}}, + "f": (1, 2, 3, 4, 5, 6), + } + + a = {"a": {"b": {"c": 5}}, "d": 1, "e": {2: 3}, "f": (1, 2, 3)} + a_copy = deepcopy(a) + + b = {"a": {"B": {"C": 10}}, "d": 2, "e": 2, "f": (4, 5, 6)} + b_copy = deepcopy(b) + + c = {"a": {"b": {"_c": 15}}, "d": 3, "e": {1: 2, "a": {"f": 2}}} + c_copy = deepcopy(c) + + actual = merge({}, a, b, c, strategy=Strategy.ADDITIVE) + + self.assertEqual(actual, expected) + self.assertEqual(a, a_copy) + self.assertEqual(b, b_copy) + self.assertEqual(c, c_copy) + + def test_should_merge_3_dicts_into_new_dict_using_additive_strategy_on_counters_and_only_mutate_target(self,): + expected = { + "a": {"b": {"c": 5, "_c": 15}, "B": {"C": 10}}, + "d": 3, + "e": {1: 2, "a": {"f": 2}}, + "f": Counter({"a": 2, "c": 1, "b": 1}), + "i": 2, + "j": Counter({"z": 2}), + "z": Counter({"a": 4}), + } + + a = { + "a": {"b": {"c": 5}}, + "d": 1, + "e": {2: 3}, + "f": Counter({"a": 1, "c": 1}), + "i": Counter({"f": 9}), + "j": Counter({"a": 1, "z": 4}), + } + a_copy = deepcopy(a) + + b = { + "a": {"B": {"C": 10}}, + "d": 2, + "e": 2, + "f": Counter({"a": 1, "b": 1}), + "j": [1, 2, 3], + "z": Counter({"a": 2}), + } + b_copy = deepcopy(b) + + c = { + "a": {"b": {"_c": 15}}, + "d": 3, + "e": {1: 2, "a": {"f": 2}}, + "i": 2, + "j": Counter({"z": 2}), + "z": Counter({"a": 2}), + } + c_copy = deepcopy(c) + + actual = merge({}, a, b, c, strategy=Strategy.ADDITIVE) + + self.assertEqual(actual, expected) + self.assertEqual(a, a_copy) + self.assertEqual(b, b_copy) + self.assertEqual(c, c_copy) + + def test_should_not_copy_references(self): + before = 1 + after = 99 + + o1 = {"key1": before} + o2 = {"key2": before} + + expected = {"list": deepcopy([o1, o2]), "tuple": deepcopy((o1, o2))} + + a = {"list": [o1], "tuple": (o1,)} + b = {"list": [o2], "tuple": (o2,)} + + actual = merge({}, a, b, strategy=Strategy.ADDITIVE) + + o1["key1"] = after + o2["key2"] = after + + self.assertEqual(actual, expected) + + # Copied dicts should `not` mutate + self.assertEqual(actual["list"][0]["key1"], before) + self.assertEqual(actual["list"][1]["key2"], before) + self.assertEqual(actual["tuple"][0]["key1"], before) + self.assertEqual(actual["tuple"][1]["key2"], before) + + # Non-copied dicts should mutate + self.assertEqual(a["list"][0]["key1"], after) + self.assertEqual(b["list"][0]["key2"], after) + self.assertEqual(a["tuple"][0]["key1"], after) + self.assertEqual(b["tuple"][0]["key2"], after) + + ############################################################################################################################## + # TYPESAFE + # TYPESAFE_REPLACE + ############################################################################################################################## + + def test_should_raise_TypeError_using_typesafe_strategy_if_types_differ(self): + a = {"a": {"b": {"c": 5}}, "d": 1, "e": {2: 3}, "f": [1, 2, 3]} + b = {"a": {"B": {"C": 10}}, "d": 2, "e": 2, "f": [4, 5, 6]} + c = {"a": {"b": {"_c": 15}}, "d": 3, "e": {1: 2, "a": {"f": 2}}} + + with self.assertRaises(TypeError): + merge({}, a, b, c, strategy=Strategy.TYPESAFE) + + def test_should_raise_TypeError_using_typesafe_replace_strategy_if_types_differ(self,): + a = {"a": {"b": {"c": 5}}, "d": 1, "e": {2: 3}, "f": [1, 2, 3]} + b = {"a": {"B": {"C": 10}}, "d": 2, "e": 2, "f": [4, 5, 6]} + c = {"a": {"b": {"_c": 15}}, "d": 3, "e": {1: 2, "a": {"f": 2}}} + + with self.assertRaises(TypeError): + merge({}, a, b, c, strategy=Strategy.TYPESAFE_REPLACE) + + def test_should_merge_3_dicts_into_new_dict_using_typesafe_strategy_and_only_mutate_target_if_types_are_compatible( + self, + ): + expected = { + "a": {"b": {"c": 5, "_c": 15}, "B": {"C": 10}}, + "d": 3, + "f": [4, 5, 6], + "g": {2, 3, 4}, + "h": (1, 3), + "z": Counter({"a": 1, "b": 1, "c": 1}), + } + + a = {"a": {"b": {"c": 5}}, "d": 1, "f": [1, 2, 3], "g": {1, 2, 3}, "z": Counter({"a": 1, "b": 1})} + a_copy = deepcopy(a) + + b = {"a": {"B": {"C": 10}}, "d": 2, "f": [4, 5, 6], "g": {2, 3, 4}, "h": (1,)} + b_copy = deepcopy(b) + + c = {"a": {"b": {"_c": 15}}, "d": 3, "h": (1, 3), "z": Counter({"a": 1, "c": 1})} + c_copy = deepcopy(c) + + actual = merge({}, a, b, c, strategy=Strategy.TYPESAFE) + + self.assertEqual(actual, expected) + self.assertEqual(a, a_copy) + self.assertEqual(b, b_copy) + self.assertEqual(c, c_copy) + + def test_should_merge_3_dicts_into_new_dict_using_typesafe_replace_strategy_and_only_mutate_target_if_types_are_compatible( + self, + ): + expected = { + "a": {"b": {"c": 5, "_c": 15}, "B": {"C": 10}}, + "d": 3, + "f": [4, 5, 6], + "g": {2, 3, 4}, + "h": (1, 3), + "z": Counter({"a": 1, "b": 1, "c": 1}), + } + + a = {"a": {"b": {"c": 5}}, "d": 1, "f": [1, 2, 3], "g": {1, 2, 3}, "z": Counter({"a": 1, "b": 1})} + a_copy = deepcopy(a) + + b = {"a": {"B": {"C": 10}}, "d": 2, "f": [4, 5, 6], "g": {2, 3, 4}, "h": (1,)} + b_copy = deepcopy(b) + + c = {"a": {"b": {"_c": 15}}, "d": 3, "h": (1, 3), "z": Counter({"a": 1, "c": 1})} + c_copy = deepcopy(c) + + actual = merge({}, a, b, c, strategy=Strategy.TYPESAFE_REPLACE) + + self.assertEqual(actual, expected) + self.assertEqual(a, a_copy) + self.assertEqual(b, b_copy) + self.assertEqual(c, c_copy) + + ############################################################################################################################## + # TYPESAFE_ADDITIVE + ############################################################################################################################## + + def test_should_raise_TypeError_using_typesafe_additive_strategy_if_types_differ(self,): + a = {"a": {"b": {"c": 5}}, "d": 1, "e": {2: 3}, "f": [1, 2, 3]} + b = {"a": {"B": {"C": 10}}, "d": 2, "e": 2, "f": [4, 5, 6]} + c = {"a": {"b": {"_c": 15}}, "d": 3, "e": {1: 2, "a": {"f": 2}}} + + with self.assertRaises(TypeError): + merge({}, a, b, c, strategy=Strategy.TYPESAFE_ADDITIVE) + + def test_should_merge_3_dicts_into_new_dict_using_typesafe_additive_strategy_and_only_mutate_target_if_types_are_compatible( + self, + ): + expected = { + "a": {"b": {"c": 5, "_c": 15}, "B": {"C": 10}}, + "d": 3, + "f": [1, 2, 3, 4, 5, 6], + "g": {1, 2, 3, 4}, + "h": (1, 1, 3), + "z": Counter({"a": 2, "b": 1, "c": 1}), + } + + a = {"a": {"b": {"c": 5}}, "d": 1, "f": [1, 2, 3], "g": {1, 2, 3}, "z": Counter({"a": 1, "b": 1})} + a_copy = deepcopy(a) + + b = {"a": {"B": {"C": 10}}, "d": 2, "f": [4, 5, 6], "g": {2, 3, 4}, "h": (1,)} + b_copy = deepcopy(b) + + c = {"a": {"b": {"_c": 15}}, "d": 3, "h": (1, 3), "z": Counter({"a": 1, "c": 1})} + c_copy = deepcopy(c) + + actual = merge({}, a, b, c, strategy=Strategy.TYPESAFE_ADDITIVE) + + self.assertEqual(actual, expected) + self.assertEqual(a, a_copy) + self.assertEqual(b, b_copy) + self.assertEqual(c, c_copy) + + +if __name__ == "__main__": + unittest.main() diff --git a/contrib/python/mergedeep/tests/ya.make b/contrib/python/mergedeep/tests/ya.make new file mode 100644 index 00000000000..a0a227f788e --- /dev/null +++ b/contrib/python/mergedeep/tests/ya.make @@ -0,0 +1,17 @@ +PY3TEST() + +SUBSCRIBER(g:python-contrib) + +PEERDIR( + contrib/python/mergedeep +) + +SRCDIR(contrib/python/mergedeep/mergedeep) + +TEST_SRCS( + test_mergedeep.py +) + +NO_LINT() + +END() diff --git a/contrib/python/mergedeep/ya.make b/contrib/python/mergedeep/ya.make new file mode 100644 index 00000000000..ec7f03aaf4a --- /dev/null +++ b/contrib/python/mergedeep/ya.make @@ -0,0 +1,29 @@ +# Generated by devtools/yamaker (pypi). + +PY3_LIBRARY() + +SUBSCRIBER(g:python-contrib) + +VERSION(1.3.4) + +LICENSE(MIT) + +NO_LINT() + +PY_SRCS( + TOP_LEVEL + mergedeep/__init__.py + mergedeep/mergedeep.py +) + +RESOURCE_FILES( + PREFIX contrib/python/mergedeep/ + .dist-info/METADATA + .dist-info/top_level.txt +) + +END() + +RECURSE_FOR_TESTS( + tests +) diff --git a/contrib/python/pure-eval/.dist-info/METADATA b/contrib/python/pure-eval/.dist-info/METADATA index 931f69c3484..1f086de3a8a 100644 --- a/contrib/python/pure-eval/.dist-info/METADATA +++ b/contrib/python/pure-eval/.dist-info/METADATA @@ -1,19 +1,19 @@ Metadata-Version: 2.1 -Name: pure-eval -Version: 0.2.2 +Name: pure_eval +Version: 0.2.3 Summary: Safely evaluate AST nodes without side effects Home-page: http://github.com/alexmojaki/pure_eval Author: Alex Hall Author-email: [email protected] License: MIT -Platform: UNKNOWN Classifier: Intended Audience :: Developers -Classifier: Programming Language :: Python :: 3.5 -Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 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: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Description-Content-Type: text/markdown @@ -23,7 +23,7 @@ Requires-Dist: pytest ; extra == 'tests' # `pure_eval` -[](https://travis-ci.org/alexmojaki/pure_eval) [](https://coveralls.io/github/alexmojaki/pure_eval?branch=master) [](https://pypi.python.org/pypi/pure_eval) +[](https://travis-ci.org/alexmojaki/pure_eval) [](https://coveralls.io/github/alexmojaki/pure_eval?branch=master) [](https://pypi.python.org/pypi/pure_eval) This is a Python package that lets you safely evaluate certain AST nodes without triggering arbitrary code that may have unwanted side effects. @@ -225,5 +225,3 @@ d = {1: 2} y = 2 d[x] = 2 ``` - - diff --git a/contrib/python/pure-eval/README.md b/contrib/python/pure-eval/README.md index a4edbfc0a57..3506b333988 100644 --- a/contrib/python/pure-eval/README.md +++ b/contrib/python/pure-eval/README.md @@ -1,6 +1,6 @@ # `pure_eval` -[](https://travis-ci.org/alexmojaki/pure_eval) [](https://coveralls.io/github/alexmojaki/pure_eval?branch=master) [](https://pypi.python.org/pypi/pure_eval) +[](https://travis-ci.org/alexmojaki/pure_eval) [](https://coveralls.io/github/alexmojaki/pure_eval?branch=master) [](https://pypi.python.org/pypi/pure_eval) This is a Python package that lets you safely evaluate certain AST nodes without triggering arbitrary code that may have unwanted side effects. diff --git a/contrib/python/pure-eval/pure_eval/__init__.py b/contrib/python/pure-eval/pure_eval/__init__.py index 0040e318a6e..ec4ba735030 100644 --- a/contrib/python/pure-eval/pure_eval/__init__.py +++ b/contrib/python/pure-eval/pure_eval/__init__.py @@ -6,3 +6,12 @@ try: except ImportError: # version.py is auto-generated with the git tag when building __version__ = "???" + +__all__ = [ + "Evaluator", + "CannotEval", + "group_expressions", + "is_expression_interesting", + "getattr_static", + "__version__", +] diff --git a/contrib/python/pure-eval/pure_eval/my_getattr_static.py b/contrib/python/pure-eval/pure_eval/my_getattr_static.py index c750b1acc3f..5490b90adec 100644 --- a/contrib/python/pure-eval/pure_eval/my_getattr_static.py +++ b/contrib/python/pure-eval/pure_eval/my_getattr_static.py @@ -79,8 +79,10 @@ def getattr_static(obj, attr): klass_result = _check_class(klass, attr) if instance_result is not _sentinel and klass_result is not _sentinel: - if (_check_class(type(klass_result), '__get__') is not _sentinel and - _check_class(type(klass_result), '__set__') is not _sentinel): + if _check_class(type(klass_result), "__get__") is not _sentinel and ( + _check_class(type(klass_result), "__set__") is not _sentinel + or _check_class(type(klass_result), "__delete__") is not _sentinel + ): return _resolve_descriptor(klass_result, obj, klass) if instance_result is not _sentinel: diff --git a/contrib/python/pure-eval/pure_eval/utils.py b/contrib/python/pure-eval/pure_eval/utils.py index a8a37302daa..19ead658506 100644 --- a/contrib/python/pure-eval/pure_eval/utils.py +++ b/contrib/python/pure-eval/pure_eval/utils.py @@ -184,7 +184,12 @@ def copy_ast_without_context(x): if field != 'ctx' if hasattr(x, field) } - return type(x)(**kwargs) + a = type(x)(**kwargs) + if hasattr(a, 'ctx'): + # Python 3.13.0b2+ defaults to Load when we don't pass ctx + # https://github.com/python/cpython/pull/118871 + del a.ctx + return a elif isinstance(x, list): return list(map(copy_ast_without_context, x)) else: diff --git a/contrib/python/pure-eval/pure_eval/version.py b/contrib/python/pure-eval/pure_eval/version.py index 9dd16a34511..db9cf742db5 100644 --- a/contrib/python/pure-eval/pure_eval/version.py +++ b/contrib/python/pure-eval/pure_eval/version.py @@ -1 +1 @@ -__version__ = '0.2.2'
\ No newline at end of file +__version__ = '0.2.3'
\ No newline at end of file diff --git a/contrib/python/pure-eval/ya.make b/contrib/python/pure-eval/ya.make index 9d3cdb4c6da..b42f4018d61 100644 --- a/contrib/python/pure-eval/ya.make +++ b/contrib/python/pure-eval/ya.make @@ -2,7 +2,7 @@ PY3_LIBRARY() -VERSION(0.2.2) +VERSION(0.2.3) LICENSE(MIT) diff --git a/contrib/python/pytest-lazy-fixtures/.dist-info/METADATA b/contrib/python/pytest-lazy-fixtures/.dist-info/METADATA index 260a9b18ccf..8b36c5866f6 100644 --- a/contrib/python/pytest-lazy-fixtures/.dist-info/METADATA +++ b/contrib/python/pytest-lazy-fixtures/.dist-info/METADATA @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: pytest-lazy-fixtures -Version: 1.0.7 +Version: 1.1.1 Summary: Allows you to use fixtures in @pytest.mark.parametrize. Home-page: https://github.com/dev-petrov/pytest-lazy-fixtures License: MIT @@ -43,7 +43,7 @@ pip install pytest-lazy-fixtures ## Usage -To use your fixtures inside `@pytest.mark.parametrize` you can use `lf` (`lazy_fixture`) or `pytest.lazy_fixtures`. +To use your fixtures inside `@pytest.mark.parametrize` you can use `lf` (`lazy_fixture`). ```python import pytest @@ -56,10 +56,6 @@ def one(): @pytest.mark.parametrize('arg1,arg2', [('val1', lf('one'))]) def test_func(arg1, arg2): assert arg2 == 1 - [email protected]('arg1,arg2', [('val1', pytest.lazy_fixtures('one'))]) -def test_func(arg1, arg2): - assert arg2 == 1 ``` `lf` can be used with any data structures. For example, in the following example, `lf` is used in the dictionary: @@ -96,7 +92,7 @@ def test_func(arg1, arg2): assert arg2 == 1 ``` -And there is some useful wrapper called `lfc` (`lazy_fixture_callable`) or `pytest.lazy_fixtures_callable`. +And there is some useful wrapper called `lfc` (`lazy_fixture_callable`). It can work with any callable and your fixtures, e.g. ```python diff --git a/contrib/python/pytest-lazy-fixtures/README.md b/contrib/python/pytest-lazy-fixtures/README.md index 6c13ec033a7..ba6ff870da3 100644 --- a/contrib/python/pytest-lazy-fixtures/README.md +++ b/contrib/python/pytest-lazy-fixtures/README.md @@ -22,7 +22,7 @@ pip install pytest-lazy-fixtures ## Usage -To use your fixtures inside `@pytest.mark.parametrize` you can use `lf` (`lazy_fixture`) or `pytest.lazy_fixtures`. +To use your fixtures inside `@pytest.mark.parametrize` you can use `lf` (`lazy_fixture`). ```python import pytest @@ -35,10 +35,6 @@ def one(): @pytest.mark.parametrize('arg1,arg2', [('val1', lf('one'))]) def test_func(arg1, arg2): assert arg2 == 1 - [email protected]('arg1,arg2', [('val1', pytest.lazy_fixtures('one'))]) -def test_func(arg1, arg2): - assert arg2 == 1 ``` `lf` can be used with any data structures. For example, in the following example, `lf` is used in the dictionary: @@ -75,7 +71,7 @@ def test_func(arg1, arg2): assert arg2 == 1 ``` -And there is some useful wrapper called `lfc` (`lazy_fixture_callable`) or `pytest.lazy_fixtures_callable`. +And there is some useful wrapper called `lfc` (`lazy_fixture_callable`). It can work with any callable and your fixtures, e.g. ```python diff --git a/contrib/python/pytest-lazy-fixtures/pytest_lazy_fixtures/loader.py b/contrib/python/pytest-lazy-fixtures/pytest_lazy_fixtures/loader.py index 3c675c2aaf1..d2d39ff4f80 100644 --- a/contrib/python/pytest-lazy-fixtures/pytest_lazy_fixtures/loader.py +++ b/contrib/python/pytest-lazy-fixtures/pytest_lazy_fixtures/loader.py @@ -14,7 +14,7 @@ def load_lazy_fixtures(value, request: pytest.FixtureRequest): return value.load_fixture(request) # we need to check exact type if type(value) is dict: # noqa: E721 - return {key: load_lazy_fixtures(value, request) for key, value in value.items()} + return {load_lazy_fixtures(key, request): load_lazy_fixtures(value, request) for key, value in value.items()} # we need to check exact type elif type(value) in {list, tuple, set}: return type(value)([load_lazy_fixtures(value, request) for value in value]) diff --git a/contrib/python/pytest-lazy-fixtures/pytest_lazy_fixtures/normalizer.py b/contrib/python/pytest-lazy-fixtures/pytest_lazy_fixtures/normalizer.py index fdbfe86402b..db4cfa8b00b 100644 --- a/contrib/python/pytest-lazy-fixtures/pytest_lazy_fixtures/normalizer.py +++ b/contrib/python/pytest-lazy-fixtures/pytest_lazy_fixtures/normalizer.py @@ -64,14 +64,15 @@ def _normalize_call(callspec, metafunc, used_keys): fm = metafunc.config.pluginmanager.get_plugin("funcmanage") used_keys = used_keys or set() - valtype_keys = callspec.params.keys() - used_keys + params = callspec.params.copy() if pytest.version_tuple >= (8, 0, 0) else {**callspec.params, **callspec.funcargs} + valtype_keys = params.keys() - used_keys for arg in valtype_keys: - value = callspec.params[arg] + value = params[arg] fixturenames_closure, arg2fixturedefs = _get_fixturenames_closure_and_arg2fixturedefs(fm, metafunc, value) if fixturenames_closure and arg2fixturedefs: - extra_fixturenames = [fname for fname in fixturenames_closure if fname not in callspec.params] + extra_fixturenames = [fname for fname in fixturenames_closure if fname not in params] newmetafunc = _copy_metafunc(metafunc) newmetafunc.fixturenames = extra_fixturenames diff --git a/contrib/python/pytest-lazy-fixtures/pytest_lazy_fixtures/plugin.py b/contrib/python/pytest-lazy-fixtures/pytest_lazy_fixtures/plugin.py index de7d75a4f79..f3475e199c8 100644 --- a/contrib/python/pytest-lazy-fixtures/pytest_lazy_fixtures/plugin.py +++ b/contrib/python/pytest-lazy-fixtures/pytest_lazy_fixtures/plugin.py @@ -1,16 +1,10 @@ import pytest -from .lazy_fixture import LazyFixtureWrapper, lf -from .lazy_fixture_callable import lfc +from .lazy_fixture import LazyFixtureWrapper from .loader import load_lazy_fixtures from .normalizer import normalize_metafunc_calls -def pytest_configure(): - pytest.lazy_fixtures = lf - pytest.lazy_fixtures_callable = lfc - - @pytest.hookimpl(tryfirst=True) def pytest_fixture_setup(fixturedef, request): val = getattr(request, "param", None) diff --git a/contrib/python/pytest-lazy-fixtures/ya.make b/contrib/python/pytest-lazy-fixtures/ya.make index b672f39330f..43c43a87f9d 100644 --- a/contrib/python/pytest-lazy-fixtures/ya.make +++ b/contrib/python/pytest-lazy-fixtures/ya.make @@ -2,7 +2,7 @@ PY3_LIBRARY() -VERSION(1.0.7) +VERSION(1.1.1) LICENSE(MIT) |
