summaryrefslogtreecommitdiffstats
path: root/contrib/python
diff options
context:
space:
mode:
authorAlexander Smirnov <[email protected]>2024-08-23 11:56:16 +0000
committerAlexander Smirnov <[email protected]>2024-08-23 11:56:16 +0000
commit5f5100feff999707cfc58fad7105d15f60741b16 (patch)
tree06c83487d372b0557078eeb16bb963780cfb3c2d /contrib/python
parent08f2dfb5d1e981a372b00e670f13ae6e5a3dbbef (diff)
parent5741cf990d59e786aee8074fc01db7655490bcfd (diff)
Merge branch 'rightlib' into mergelibs-240823-1155
Diffstat (limited to 'contrib/python')
-rw-r--r--contrib/python/argcomplete/py3/.dist-info/METADATA2
-rw-r--r--contrib/python/argcomplete/py3/.dist-info/entry_points.txt4
-rw-r--r--contrib/python/argcomplete/py3/argcomplete/bash_completion.d/_python-argcomplete1
-rw-r--r--contrib/python/argcomplete/py3/argcomplete/completers.py2
-rw-r--r--contrib/python/argcomplete/py3/argcomplete/finders.py12
-rw-r--r--contrib/python/argcomplete/py3/argcomplete/scripts/__init__.py0
-rw-r--r--contrib/python/argcomplete/py3/argcomplete/scripts/activate_global_python_argcomplete.py168
-rw-r--r--contrib/python/argcomplete/py3/argcomplete/scripts/python_argcomplete_check_easy_install_script.py84
-rw-r--r--contrib/python/argcomplete/py3/argcomplete/scripts/register_python_argcomplete.py79
-rw-r--r--contrib/python/argcomplete/py3/ya.make7
-rw-r--r--contrib/python/google-auth/py3/.dist-info/METADATA2
-rw-r--r--contrib/python/google-auth/py3/google/auth/_credentials_base.py75
-rw-r--r--contrib/python/google-auth/py3/google/auth/_exponential_backoff.py10
-rw-r--r--contrib/python/google-auth/py3/google/auth/aio/__init__.py25
-rw-r--r--contrib/python/google-auth/py3/google/auth/aio/credentials.py143
-rw-r--r--contrib/python/google-auth/py3/google/auth/compute_engine/_metadata.py21
-rw-r--r--contrib/python/google-auth/py3/google/auth/credentials.py12
-rw-r--r--contrib/python/google-auth/py3/google/auth/transport/_requests_base.py52
-rw-r--r--contrib/python/google-auth/py3/google/auth/transport/requests.py5
-rw-r--r--contrib/python/google-auth/py3/google/auth/version.py2
-rw-r--r--contrib/python/google-auth/py3/google/oauth2/_client.py22
-rw-r--r--contrib/python/google-auth/py3/google/oauth2/_client_async.py20
-rw-r--r--contrib/python/google-auth/py3/tests/compute_engine/test__metadata.py15
-rw-r--r--contrib/python/google-auth/py3/tests/oauth2/test__client.py10
-rw-r--r--contrib/python/google-auth/py3/tests/test__exponential_backoff.py31
-rw-r--r--contrib/python/google-auth/py3/tests/test_credentials_async.py136
-rw-r--r--contrib/python/google-auth/py3/ya.make6
-rw-r--r--contrib/python/hypothesis/py3/.dist-info/METADATA78
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/extra/django/_impl.py2
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/internal/cathetus.py2
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/internal/conjecture/data.py10
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/internal/conjecture/engine.py5
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/internal/conjecture/junkdrawer.py18
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/internal/conjecture/optimiser.py39
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/internal/conjecture/shrinker.py40
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/internal/conjecture/shrinking/collection.py5
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/internal/conjecture/shrinking/common.py6
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/internal/conjecture/shrinking/ordering.py5
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/internal/conjecture/utils.py4
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/internal/floats.py66
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/strategies/_internal/core.py83
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/strategies/_internal/numbers.py16
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/strategies/_internal/types.py53
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/version.py2
-rw-r--r--contrib/python/hypothesis/py3/ya.make2
-rw-r--r--contrib/python/matplotlib/py2/extern/agg24-svn/ya.make2
-rw-r--r--contrib/python/matplotlib/py2/extern/ttconv/ya.make2
-rw-r--r--contrib/python/matplotlib/py2/matplotlib/tri/ya.make2
-rw-r--r--contrib/python/matplotlib/py2/src/ya.make2
-rw-r--r--contrib/python/more-itertools/py3/.dist-info/METADATA4
-rw-r--r--contrib/python/more-itertools/py3/README.rst2
-rw-r--r--contrib/python/more-itertools/py3/more_itertools/__init__.py2
-rw-r--r--contrib/python/more-itertools/py3/more_itertools/more.py326
-rw-r--r--contrib/python/more-itertools/py3/more_itertools/more.pyi128
-rw-r--r--contrib/python/more-itertools/py3/more_itertools/recipes.py48
-rw-r--r--contrib/python/more-itertools/py3/tests/test_more.py172
-rw-r--r--contrib/python/more-itertools/py3/tests/test_recipes.py13
-rw-r--r--contrib/python/more-itertools/py3/ya.make2
-rw-r--r--contrib/python/numpy/py2/numpy/core/src/multiarray/ya.make2
-rw-r--r--contrib/python/numpy/py2/numpy/core/src/umath/ya.make2
-rw-r--r--contrib/python/numpy/py2/numpy/f2py/ya.make2
-rw-r--r--contrib/python/numpy/py2/numpy/fft/ya.make2
-rw-r--r--contrib/python/numpy/py2/numpy/linalg/ya.make2
-rw-r--r--contrib/python/numpy/py2/numpy/random/mtrand/ya.make2
-rw-r--r--contrib/python/numpy/py3/numpy/random/ya.make2
-rw-r--r--contrib/python/ydb/py3/.dist-info/METADATA2
-rw-r--r--contrib/python/ydb/py3/ya.make7
-rw-r--r--contrib/python/ydb/py3/ydb/_grpc/grpcwrapper/common_utils.py9
-rw-r--r--contrib/python/ydb/py3/ydb/aio/__init__.py1
-rw-r--r--contrib/python/ydb/py3/ydb/aio/_utilities.py3
-rw-r--r--contrib/python/ydb/py3/ydb/aio/query/__init__.py7
-rw-r--r--contrib/python/ydb/py3/ydb/aio/query/base.py11
-rw-r--r--contrib/python/ydb/py3/ydb/aio/query/pool.py108
-rw-r--r--contrib/python/ydb/py3/ydb/aio/query/session.py144
-rw-r--r--contrib/python/ydb/py3/ydb/aio/query/transaction.py153
-rw-r--r--contrib/python/ydb/py3/ydb/query/__init__.py7
-rw-r--r--contrib/python/ydb/py3/ydb/query/base.py264
-rw-r--r--contrib/python/ydb/py3/ydb/query/pool.py26
-rw-r--r--contrib/python/ydb/py3/ydb/query/session.py24
-rw-r--r--contrib/python/ydb/py3/ydb/query/transaction.py108
-rw-r--r--contrib/python/ydb/py3/ydb/retries.py25
-rw-r--r--contrib/python/ydb/py3/ydb/ydb_version.py2
82 files changed, 2343 insertions, 659 deletions
diff --git a/contrib/python/argcomplete/py3/.dist-info/METADATA b/contrib/python/argcomplete/py3/.dist-info/METADATA
index adbd1293a61..ce75d7bfa8b 100644
--- a/contrib/python/argcomplete/py3/.dist-info/METADATA
+++ b/contrib/python/argcomplete/py3/.dist-info/METADATA
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: argcomplete
-Version: 3.4.0
+Version: 3.5.0
Summary: Bash tab completion for argparse
Home-page: https://github.com/kislyuk/argcomplete
Author: Andrey Kislyuk
diff --git a/contrib/python/argcomplete/py3/.dist-info/entry_points.txt b/contrib/python/argcomplete/py3/.dist-info/entry_points.txt
new file mode 100644
index 00000000000..f13e435fafb
--- /dev/null
+++ b/contrib/python/argcomplete/py3/.dist-info/entry_points.txt
@@ -0,0 +1,4 @@
+[console_scripts]
+activate-global-python-argcomplete = argcomplete.scripts.activate_global_python_argcomplete:main
+python-argcomplete-check-easy-install-script = argcomplete.scripts.python_argcomplete_check_easy_install_script:main
+register-python-argcomplete = argcomplete.scripts.register_python_argcomplete:main
diff --git a/contrib/python/argcomplete/py3/argcomplete/bash_completion.d/_python-argcomplete b/contrib/python/argcomplete/py3/argcomplete/bash_completion.d/_python-argcomplete
index dec7cdac16d..81c9d41f803 100644
--- a/contrib/python/argcomplete/py3/argcomplete/bash_completion.d/_python-argcomplete
+++ b/contrib/python/argcomplete/py3/argcomplete/bash_completion.d/_python-argcomplete
@@ -1,5 +1,6 @@
#compdef -default-
+# argcomplete global completion loader for zsh and bash
# Copyright 2012-2023, Andrey Kislyuk and argcomplete contributors.
# Licensed under the Apache License. See https://github.com/kislyuk/argcomplete for more info.
diff --git a/contrib/python/argcomplete/py3/argcomplete/completers.py b/contrib/python/argcomplete/py3/argcomplete/completers.py
index d89cdbfbc34..b62c593d64f 100644
--- a/contrib/python/argcomplete/py3/argcomplete/completers.py
+++ b/contrib/python/argcomplete/py3/argcomplete/completers.py
@@ -22,7 +22,7 @@ class BaseCompleter:
def __call__(
self, *, prefix: str, action: argparse.Action, parser: argparse.ArgumentParser, parsed_args: argparse.Namespace
- ):
+ ) -> None:
raise NotImplementedError("This method should be implemented by a subclass.")
diff --git a/contrib/python/argcomplete/py3/argcomplete/finders.py b/contrib/python/argcomplete/py3/argcomplete/finders.py
index fb0f31cefd9..793b462eed0 100644
--- a/contrib/python/argcomplete/py3/argcomplete/finders.py
+++ b/contrib/python/argcomplete/py3/argcomplete/finders.py
@@ -7,10 +7,10 @@ import argparse
import os
import sys
from collections.abc import Mapping
-from typing import Callable, Dict, List, Optional, Sequence, Union
+from typing import Callable, Dict, List, Optional, Sequence, TextIO, Union
from . import io as _io
-from .completers import ChoicesCompleter, FilesCompleter, SuppressCompleter
+from .completers import BaseCompleter, ChoicesCompleter, FilesCompleter, SuppressCompleter
from .io import debug, mute_stderr
from .lexers import split_line
from .packages._argparse import IntrospectiveArgumentParser, action_is_greedy, action_is_open, action_is_satisfied
@@ -66,13 +66,13 @@ class CompletionFinder(object):
argument_parser: argparse.ArgumentParser,
always_complete_options: Union[bool, str] = True,
exit_method: Callable = os._exit,
- output_stream=None,
+ output_stream: Optional[TextIO] = None,
exclude: Optional[Sequence[str]] = None,
validator: Optional[Callable] = None,
print_suppressed: bool = False,
append_space: Optional[bool] = None,
- default_completer=FilesCompleter(),
- ):
+ default_completer: BaseCompleter = FilesCompleter(),
+ ) -> None:
"""
:param argument_parser: The argument parser to autocomplete on
:param always_complete_options:
@@ -132,6 +132,8 @@ class CompletionFinder(object):
debug("Unable to open fd 8 for writing, quitting")
exit_method(1)
+ assert output_stream is not None
+
ifs = os.environ.get("_ARGCOMPLETE_IFS", "\013")
if len(ifs) != 1:
debug("Invalid value for IFS, quitting [{v}]".format(v=ifs))
diff --git a/contrib/python/argcomplete/py3/argcomplete/scripts/__init__.py b/contrib/python/argcomplete/py3/argcomplete/scripts/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/contrib/python/argcomplete/py3/argcomplete/scripts/__init__.py
diff --git a/contrib/python/argcomplete/py3/argcomplete/scripts/activate_global_python_argcomplete.py b/contrib/python/argcomplete/py3/argcomplete/scripts/activate_global_python_argcomplete.py
new file mode 100644
index 00000000000..768b8aa6bf3
--- /dev/null
+++ b/contrib/python/argcomplete/py3/argcomplete/scripts/activate_global_python_argcomplete.py
@@ -0,0 +1,168 @@
+#!/usr/bin/env python3
+# PYTHON_ARGCOMPLETE_OK
+
+# Copyright 2012-2023, Andrey Kislyuk and argcomplete contributors.
+# Licensed under the Apache License. See https://github.com/kislyuk/argcomplete for more info.
+
+"""
+Activate the generic bash-completion script or zsh completion autoload function for the argcomplete module.
+"""
+
+import argparse
+import os
+import shutil
+import site
+import subprocess
+import sys
+
+import argcomplete
+
+# PEP 366
+__package__ = "argcomplete.scripts"
+
+zsh_shellcode = """
+# Begin added by argcomplete
+fpath=( {zsh_fpath} "${{fpath[@]}}" )
+# End added by argcomplete
+"""
+
+bash_shellcode = """
+# Begin added by argcomplete
+source "{activator}"
+# End added by argcomplete
+"""
+
+parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
+parser.add_argument("-y", "--yes", help="automatically answer yes for all questions", action="store_true")
+parser.add_argument("--dest", help='Specify the shell completion modules directory to install into, or "-" for stdout')
+parser.add_argument("--user", help="Install into user directory", action="store_true")
+argcomplete.autocomplete(parser)
+args = None
+
+
+def get_local_dir():
+ try:
+ return subprocess.check_output(["brew", "--prefix"]).decode().strip()
+ except (FileNotFoundError, subprocess.CalledProcessError):
+ return "/usr/local"
+
+
+def get_zsh_system_dir():
+ return f"{get_local_dir()}/share/zsh/site-functions"
+
+
+def get_bash_system_dir():
+ if "BASH_COMPLETION_COMPAT_DIR" in os.environ:
+ return os.environ["BASH_COMPLETION_COMPAT_DIR"]
+ elif sys.platform == "darwin":
+ return f"{get_local_dir()}/etc/bash_completion.d" # created by homebrew
+ else:
+ return "/etc/bash_completion.d" # created by bash-completion
+
+
+def get_activator_dir():
+ return os.path.join(os.path.abspath(os.path.dirname(argcomplete.__file__)), "bash_completion.d")
+
+
+def get_activator_path():
+ return os.path.join(get_activator_dir(), "_python-argcomplete")
+
+
+def install_to_destination(dest):
+ activator = get_activator_path()
+ if dest == "-":
+ with open(activator) as fh:
+ sys.stdout.write(fh.read())
+ return
+ destdir = os.path.dirname(dest)
+ if not os.path.exists(destdir):
+ try:
+ os.makedirs(destdir, exist_ok=True)
+ except Exception as e:
+ parser.error(f"path {destdir} does not exist and could not be created: {e}")
+ try:
+ print(f"Installing {activator} to {dest}...", file=sys.stderr)
+ shutil.copy(activator, dest)
+ print("Installed.", file=sys.stderr)
+ except Exception as e:
+ parser.error(
+ f"while installing to {dest}: {e}. Please run this command using sudo, or see --help for more options."
+ )
+
+
+def get_consent():
+ assert args is not None
+ if args.yes is True:
+ return True
+ while True:
+ res = input("OK to proceed? [y/n] ")
+ if res.lower() not in {"y", "n", "yes", "no"}:
+ print('Please answer "yes" or "no".', file=sys.stderr)
+ elif res.lower() in {"y", "yes"}:
+ return True
+ else:
+ return False
+
+
+def append_to_config_file(path, shellcode):
+ if os.path.exists(path):
+ with open(path, 'r') as fh:
+ if shellcode in fh.read():
+ print(f"The code already exists in the file {path}.", file=sys.stderr)
+ return
+ print(f"argcomplete needs to append to the file {path}. The following code will be appended:", file=sys.stderr)
+ for line in shellcode.splitlines():
+ print(">", line, file=sys.stderr)
+ if not get_consent():
+ print("Not added.", file=sys.stderr)
+ return
+ print(f"Adding shellcode to {path}...", file=sys.stderr)
+ with open(path, "a") as fh:
+ fh.write(shellcode)
+ print("Added.", file=sys.stderr)
+
+
+def link_user_rcfiles():
+ # TODO: warn if running as superuser
+ zsh_rcfile = os.path.join(os.path.expanduser(os.environ.get("ZDOTDIR", "~")), ".zshenv")
+ append_to_config_file(zsh_rcfile, zsh_shellcode.format(zsh_fpath=get_activator_dir()))
+
+ bash_completion_user_file = os.path.expanduser("~/.bash_completion")
+ append_to_config_file(bash_completion_user_file, bash_shellcode.format(activator=get_activator_path()))
+
+
+def main():
+ global args
+ args = parser.parse_args()
+
+ destinations = []
+
+ if args.dest:
+ if args.dest != "-" and not os.path.exists(args.dest):
+ parser.error(f"directory {args.dest} was specified via --dest, but it does not exist")
+ destinations.append(args.dest)
+ elif site.ENABLE_USER_SITE and site.USER_SITE and site.USER_SITE in argcomplete.__file__:
+ print(
+ "Argcomplete was installed in the user site local directory. Defaulting to user installation.",
+ file=sys.stderr,
+ )
+ link_user_rcfiles()
+ elif sys.prefix != sys.base_prefix:
+ print("Argcomplete was installed in a virtual environment. Defaulting to user installation.", file=sys.stderr)
+ link_user_rcfiles()
+ elif args.user:
+ link_user_rcfiles()
+ else:
+ print("Defaulting to system-wide installation.", file=sys.stderr)
+ destinations.append(f"{get_zsh_system_dir()}/_python-argcomplete")
+ destinations.append(f"{get_bash_system_dir()}/python-argcomplete")
+
+ for destination in destinations:
+ install_to_destination(destination)
+
+ if args.dest is None:
+ print("Please restart your shell or source the installed file to activate it.", file=sys.stderr)
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/contrib/python/argcomplete/py3/argcomplete/scripts/python_argcomplete_check_easy_install_script.py b/contrib/python/argcomplete/py3/argcomplete/scripts/python_argcomplete_check_easy_install_script.py
new file mode 100644
index 00000000000..d914c222fed
--- /dev/null
+++ b/contrib/python/argcomplete/py3/argcomplete/scripts/python_argcomplete_check_easy_install_script.py
@@ -0,0 +1,84 @@
+#!/usr/bin/env python3
+
+# Copyright 2012-2023, Andrey Kislyuk and argcomplete contributors.
+# Licensed under the Apache License. See https://github.com/kislyuk/argcomplete for more info.
+
+"""
+This script is part of the Python argcomplete package (https://github.com/kislyuk/argcomplete).
+It is used to check if an EASY-INSTALL-SCRIPT wrapper redirects to a script that contains the string
+"PYTHON_ARGCOMPLETE_OK". If you have enabled global completion in argcomplete, the completion hook will run it every
+time you press <TAB> in your shell.
+
+Usage:
+ python-argcomplete-check-easy-install-script <input executable file>
+"""
+
+import sys
+
+# PEP 366
+__package__ = "argcomplete.scripts"
+
+
+def main():
+ if len(sys.argv) != 2:
+ sys.exit(__doc__)
+
+ sys.tracebacklimit = 0
+
+ with open(sys.argv[1]) as fh:
+ line1, head = fh.read(1024).split("\n", 1)[:2]
+ if line1.startswith("#") and ("py" in line1 or "Py" in line1):
+ import re
+
+ lines = head.split("\n", 12)
+ for line in lines:
+ if line.startswith("# EASY-INSTALL-SCRIPT"):
+ import pkg_resources
+
+ re_match = re.match("# EASY-INSTALL-SCRIPT: '(.+)','(.+)'", line)
+ assert re_match is not None
+ dist, script = re_match.groups()
+ if "PYTHON_ARGCOMPLETE_OK" in pkg_resources.get_distribution(dist).get_metadata(
+ "scripts/" + script
+ ):
+ return 0
+ elif line.startswith("# EASY-INSTALL-ENTRY-SCRIPT"):
+ re_match = re.match("# EASY-INSTALL-ENTRY-SCRIPT: '(.+)','(.+)','(.+)'", line)
+ assert re_match is not None
+ dist, group, name = re_match.groups()
+ import pkgutil
+
+ import pkg_resources
+
+ entry_point_info = pkg_resources.get_distribution(dist).get_entry_info(group, name)
+ assert entry_point_info is not None
+ module_name = entry_point_info.module_name
+ with open(pkgutil.get_loader(module_name).get_filename()) as mod_fh: # type: ignore
+ if "PYTHON_ARGCOMPLETE_OK" in mod_fh.read(1024):
+ return 0
+ elif line.startswith("# EASY-INSTALL-DEV-SCRIPT"):
+ for line2 in lines:
+ if line2.startswith("__file__"):
+ re_match = re.match("__file__ = '(.+)'", line2)
+ assert re_match is not None
+ filename = re_match.group(1)
+ with open(filename) as mod_fh:
+ if "PYTHON_ARGCOMPLETE_OK" in mod_fh.read(1024):
+ return 0
+ elif line.startswith("# PBR Generated"):
+ re_match = re.search("from (.*) import", head)
+ assert re_match is not None
+ module = re_match.groups()[0]
+ import pkgutil
+
+ import pkg_resources
+
+ with open(pkgutil.get_loader(module).get_filename()) as mod_fh: # type: ignore
+ if "PYTHON_ARGCOMPLETE_OK" in mod_fh.read(1024):
+ return 0
+
+ return 1
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/contrib/python/argcomplete/py3/argcomplete/scripts/register_python_argcomplete.py b/contrib/python/argcomplete/py3/argcomplete/scripts/register_python_argcomplete.py
new file mode 100644
index 00000000000..1e680298258
--- /dev/null
+++ b/contrib/python/argcomplete/py3/argcomplete/scripts/register_python_argcomplete.py
@@ -0,0 +1,79 @@
+#!/usr/bin/env python3
+# PYTHON_ARGCOMPLETE_OK
+
+# Copyright 2012-2023, Andrey Kislyuk and argcomplete contributors.
+# Licensed under the Apache License. See https://github.com/kislyuk/argcomplete for more info.
+
+"""
+Register a Python executable for use with the argcomplete module.
+
+To perform the registration, source the output of this script in your bash shell
+(quote the output to avoid interpolation).
+
+Example:
+
+ $ eval "$(register-python-argcomplete my-favorite-script.py)"
+
+For Tcsh
+
+ $ eval `register-python-argcomplete --shell tcsh my-favorite-script.py`
+
+For Fish
+
+ $ register-python-argcomplete --shell fish my-favourite-script.py > ~/.config/fish/my-favourite-script.py.fish
+"""
+
+import argparse
+import sys
+
+import argcomplete
+
+# PEP 366
+__package__ = "argcomplete.scripts"
+
+
+def main():
+ parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
+
+ parser.add_argument(
+ "--no-defaults",
+ dest="use_defaults",
+ action="store_false",
+ default=True,
+ help="when no matches are generated, do not fallback to readline's default completion (affects bash only)",
+ )
+ parser.add_argument(
+ "--complete-arguments",
+ nargs=argparse.REMAINDER,
+ help="arguments to call complete with; use of this option discards default options (affects bash only)",
+ )
+ parser.add_argument(
+ "-s",
+ "--shell",
+ choices=("bash", "zsh", "tcsh", "fish", "powershell"),
+ default="bash",
+ help="output code for the specified shell",
+ )
+ parser.add_argument(
+ "-e", "--external-argcomplete-script", help="external argcomplete script for auto completion of the executable"
+ )
+
+ parser.add_argument("executable", nargs="+", help="executable to completed (when invoked by exactly this name)")
+
+ argcomplete.autocomplete(parser)
+
+ if len(sys.argv) == 1:
+ parser.print_help()
+ sys.exit(1)
+
+ args = parser.parse_args()
+
+ sys.stdout.write(
+ argcomplete.shellcode(
+ args.executable, args.use_defaults, args.shell, args.complete_arguments, args.external_argcomplete_script
+ )
+ )
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/contrib/python/argcomplete/py3/ya.make b/contrib/python/argcomplete/py3/ya.make
index e2cc4f65409..16487619c8f 100644
--- a/contrib/python/argcomplete/py3/ya.make
+++ b/contrib/python/argcomplete/py3/ya.make
@@ -2,7 +2,7 @@
PY3_LIBRARY()
-VERSION(3.4.0)
+VERSION(3.5.0)
LICENSE(Apache-2.0)
@@ -21,12 +21,17 @@ PY_SRCS(
argcomplete/packages/__init__.py
argcomplete/packages/_argparse.py
argcomplete/packages/_shlex.py
+ argcomplete/scripts/__init__.py
+ argcomplete/scripts/activate_global_python_argcomplete.py
+ argcomplete/scripts/python_argcomplete_check_easy_install_script.py
+ argcomplete/scripts/register_python_argcomplete.py
argcomplete/shell_integration.py
)
RESOURCE_FILES(
PREFIX contrib/python/argcomplete/py3/
.dist-info/METADATA
+ .dist-info/entry_points.txt
.dist-info/top_level.txt
argcomplete/bash_completion.d/_python-argcomplete
argcomplete/py.typed
diff --git a/contrib/python/google-auth/py3/.dist-info/METADATA b/contrib/python/google-auth/py3/.dist-info/METADATA
index 1814862af63..cdbc6833961 100644
--- a/contrib/python/google-auth/py3/.dist-info/METADATA
+++ b/contrib/python/google-auth/py3/.dist-info/METADATA
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: google-auth
-Version: 2.32.0
+Version: 2.33.0
Summary: Google Authentication Library
Home-page: https://github.com/googleapis/google-auth-library-python
Author: Google Cloud Platform
diff --git a/contrib/python/google-auth/py3/google/auth/_credentials_base.py b/contrib/python/google-auth/py3/google/auth/_credentials_base.py
new file mode 100644
index 00000000000..64d5ce34b9a
--- /dev/null
+++ b/contrib/python/google-auth/py3/google/auth/_credentials_base.py
@@ -0,0 +1,75 @@
+# Copyright 2024 Google LLC
+#
+# 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.
+
+
+"""Interface for base credentials."""
+
+import abc
+
+from google.auth import _helpers
+
+
+class _BaseCredentials(metaclass=abc.ABCMeta):
+ """Base class for all credentials.
+
+ All credentials have a :attr:`token` that is used for authentication and
+ may also optionally set an :attr:`expiry` to indicate when the token will
+ no longer be valid.
+
+ Most credentials will be :attr:`invalid` until :meth:`refresh` is called.
+ Credentials can do this automatically before the first HTTP request in
+ :meth:`before_request`.
+
+ Although the token and expiration will change as the credentials are
+ :meth:`refreshed <refresh>` and used, credentials should be considered
+ immutable. Various credentials will accept configuration such as private
+ keys, scopes, and other options. These options are not changeable after
+ construction. Some classes will provide mechanisms to copy the credentials
+ with modifications such as :meth:`ScopedCredentials.with_scopes`.
+
+ Attributes:
+ token (Optional[str]): The bearer token that can be used in HTTP headers to make
+ authenticated requests.
+ """
+
+ def __init__(self):
+ self.token = None
+
+ @abc.abstractmethod
+ def refresh(self, request):
+ """Refreshes the access token.
+
+ Args:
+ request (google.auth.transport.Request): The object used to make
+ HTTP requests.
+
+ Raises:
+ google.auth.exceptions.RefreshError: If the credentials could
+ not be refreshed.
+ """
+ # pylint: disable=missing-raises-doc
+ # (pylint doesn't recognize that this is abstract)
+ raise NotImplementedError("Refresh must be implemented")
+
+ def _apply(self, headers, token=None):
+ """Apply the token to the authentication header.
+
+ Args:
+ headers (Mapping): The HTTP request headers.
+ token (Optional[str]): If specified, overrides the current access
+ token.
+ """
+ headers["authorization"] = "Bearer {}".format(
+ _helpers.from_bytes(token or self.token)
+ )
diff --git a/contrib/python/google-auth/py3/google/auth/_exponential_backoff.py b/contrib/python/google-auth/py3/google/auth/_exponential_backoff.py
index 0dd621a9492..04f9f976412 100644
--- a/contrib/python/google-auth/py3/google/auth/_exponential_backoff.py
+++ b/contrib/python/google-auth/py3/google/auth/_exponential_backoff.py
@@ -15,6 +15,8 @@
import random
import time
+from google.auth import exceptions
+
# The default amount of retry attempts
_DEFAULT_RETRY_TOTAL_ATTEMPTS = 3
@@ -68,6 +70,11 @@ class ExponentialBackoff:
randomization_factor=_DEFAULT_RANDOMIZATION_FACTOR,
multiplier=_DEFAULT_MULTIPLIER,
):
+ if total_attempts < 1:
+ raise exceptions.InvalidValue(
+ f"total_attempts must be greater than or equal to 1 but was {total_attempts}"
+ )
+
self._total_attempts = total_attempts
self._initial_wait_seconds = initial_wait_seconds
@@ -87,6 +94,9 @@ class ExponentialBackoff:
raise StopIteration
self._backoff_count += 1
+ if self._backoff_count <= 1:
+ return self._backoff_count
+
jitter_variance = self._current_wait_in_seconds * self._randomization_factor
jitter = random.uniform(
self._current_wait_in_seconds - jitter_variance,
diff --git a/contrib/python/google-auth/py3/google/auth/aio/__init__.py b/contrib/python/google-auth/py3/google/auth/aio/__init__.py
new file mode 100644
index 00000000000..331708cba62
--- /dev/null
+++ b/contrib/python/google-auth/py3/google/auth/aio/__init__.py
@@ -0,0 +1,25 @@
+# Copyright 2024 Google LLC
+#
+# 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.
+
+"""Google Auth AIO Library for Python."""
+
+import logging
+
+from google.auth import version as google_auth_version
+
+
+__version__ = google_auth_version.__version__
+
+# Set default logging handler to avoid "No handler found" warnings.
+logging.getLogger(__name__).addHandler(logging.NullHandler())
diff --git a/contrib/python/google-auth/py3/google/auth/aio/credentials.py b/contrib/python/google-auth/py3/google/auth/aio/credentials.py
new file mode 100644
index 00000000000..3bc6a5a6762
--- /dev/null
+++ b/contrib/python/google-auth/py3/google/auth/aio/credentials.py
@@ -0,0 +1,143 @@
+# Copyright 2024 Google LLC
+#
+# 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.
+
+
+"""Interfaces for asynchronous credentials."""
+
+
+from google.auth import _helpers
+from google.auth import exceptions
+from google.auth._credentials_base import _BaseCredentials
+
+
+class Credentials(_BaseCredentials):
+ """Base class for all asynchronous credentials.
+
+ All credentials have a :attr:`token` that is used for authentication and
+ may also optionally set an :attr:`expiry` to indicate when the token will
+ no longer be valid.
+
+ Most credentials will be :attr:`invalid` until :meth:`refresh` is called.
+ Credentials can do this automatically before the first HTTP request in
+ :meth:`before_request`.
+
+ Although the token and expiration will change as the credentials are
+ :meth:`refreshed <refresh>` and used, credentials should be considered
+ immutable. Various credentials will accept configuration such as private
+ keys, scopes, and other options. These options are not changeable after
+ construction. Some classes will provide mechanisms to copy the credentials
+ with modifications such as :meth:`ScopedCredentials.with_scopes`.
+ """
+
+ def __init__(self):
+ super(Credentials, self).__init__()
+
+ async def apply(self, headers, token=None):
+ """Apply the token to the authentication header.
+
+ Args:
+ headers (Mapping): The HTTP request headers.
+ token (Optional[str]): If specified, overrides the current access
+ token.
+ """
+ self._apply(headers, token=token)
+
+ async def refresh(self, request):
+ """Refreshes the access token.
+
+ Args:
+ request (google.auth.aio.transport.Request): The object used to make
+ HTTP requests.
+
+ Raises:
+ google.auth.exceptions.RefreshError: If the credentials could
+ not be refreshed.
+ """
+ raise NotImplementedError("Refresh must be implemented")
+
+ async def before_request(self, request, method, url, headers):
+ """Performs credential-specific before request logic.
+
+ Refreshes the credentials if necessary, then calls :meth:`apply` to
+ apply the token to the authentication header.
+
+ Args:
+ request (google.auth.aio.transport.Request): The object used to make
+ HTTP requests.
+ method (str): The request's HTTP method or the RPC method being
+ invoked.
+ url (str): The request's URI or the RPC service's URI.
+ headers (Mapping): The request's headers.
+ """
+ await self.apply(headers)
+
+
+class StaticCredentials(Credentials):
+ """Asynchronous Credentials representing an immutable access token.
+
+ The credentials are considered immutable except the tokens which can be
+ configured in the constructor ::
+
+ credentials = StaticCredentials(token="token123")
+
+ StaticCredentials does not support :meth `refresh` and assumes that the configured
+ token is valid and not expired. StaticCredentials will never attempt to
+ refresh the token.
+ """
+
+ def __init__(self, token):
+ """
+ Args:
+ token (str): The access token.
+ """
+ super(StaticCredentials, self).__init__()
+ self.token = token
+
+ @_helpers.copy_docstring(Credentials)
+ async def refresh(self, request):
+ raise exceptions.InvalidOperation("Static credentials cannot be refreshed.")
+
+ # Note: before_request should never try to refresh access tokens.
+ # StaticCredentials intentionally does not support it.
+ @_helpers.copy_docstring(Credentials)
+ async def before_request(self, request, method, url, headers):
+ await self.apply(headers)
+
+
+class AnonymousCredentials(Credentials):
+ """Asynchronous Credentials that do not provide any authentication information.
+
+ These are useful in the case of services that support anonymous access or
+ local service emulators that do not use credentials.
+ """
+
+ async def refresh(self, request):
+ """Raises :class:``InvalidOperation``, anonymous credentials cannot be
+ refreshed."""
+ raise exceptions.InvalidOperation("Anonymous credentials cannot be refreshed.")
+
+ async def apply(self, headers, token=None):
+ """Anonymous credentials do nothing to the request.
+
+ The optional ``token`` argument is not supported.
+
+ Raises:
+ google.auth.exceptions.InvalidValue: If a token was specified.
+ """
+ if token is not None:
+ raise exceptions.InvalidValue("Anonymous credentials don't support tokens.")
+
+ async def before_request(self, request, method, url, headers):
+ """Anonymous credentials do nothing to the request."""
+ pass
diff --git a/contrib/python/google-auth/py3/google/auth/compute_engine/_metadata.py b/contrib/python/google-auth/py3/google/auth/compute_engine/_metadata.py
index e597365851c..69b7b524589 100644
--- a/contrib/python/google-auth/py3/google/auth/compute_engine/_metadata.py
+++ b/contrib/python/google-auth/py3/google/auth/compute_engine/_metadata.py
@@ -28,11 +28,12 @@ from google.auth import _helpers
from google.auth import environment_vars
from google.auth import exceptions
from google.auth import metrics
+from google.auth._exponential_backoff import ExponentialBackoff
_LOGGER = logging.getLogger(__name__)
# Environment variable GCE_METADATA_HOST is originally named
-# GCE_METADATA_ROOT. For compatiblity reasons, here it checks
+# GCE_METADATA_ROOT. For compatibility reasons, here it checks
# the new variable first; if not set, the system falls back
# to the old variable.
_GCE_METADATA_HOST = os.getenv(environment_vars.GCE_METADATA_HOST, None)
@@ -119,11 +120,12 @@ def ping(request, timeout=_METADATA_DEFAULT_TIMEOUT, retry_count=3):
# could lead to false negatives in the event that we are on GCE, but
# the metadata resolution was particularly slow. The latter case is
# "unlikely".
- retries = 0
headers = _METADATA_HEADERS.copy()
headers[metrics.API_CLIENT_HEADER] = metrics.mds_ping()
- while retries < retry_count:
+ backoff = ExponentialBackoff(total_attempts=retry_count)
+
+ for attempt in backoff:
try:
response = request(
url=_METADATA_IP_ROOT, method="GET", headers=headers, timeout=timeout
@@ -139,11 +141,10 @@ def ping(request, timeout=_METADATA_DEFAULT_TIMEOUT, retry_count=3):
_LOGGER.warning(
"Compute Engine Metadata server unavailable on "
"attempt %s of %s. Reason: %s",
- retries + 1,
+ attempt,
retry_count,
e,
)
- retries += 1
return False
@@ -179,7 +180,7 @@ def get(
Returns:
Union[Mapping, str]: If the metadata server returns JSON, a mapping of
- the decoded JSON is return. Otherwise, the response content is
+ the decoded JSON is returned. Otherwise, the response content is
returned as a string.
Raises:
@@ -198,8 +199,9 @@ def get(
url = _helpers.update_query(base_url, query_params)
- retries = 0
- while retries < retry_count:
+ backoff = ExponentialBackoff(total_attempts=retry_count)
+
+ for attempt in backoff:
try:
response = request(url=url, method="GET", headers=headers_to_use)
break
@@ -208,11 +210,10 @@ def get(
_LOGGER.warning(
"Compute Engine Metadata server unavailable on "
"attempt %s of %s. Reason: %s",
- retries + 1,
+ attempt,
retry_count,
e,
)
- retries += 1
else:
raise exceptions.TransportError(
"Failed to retrieve {} from the Google Compute Engine "
diff --git a/contrib/python/google-auth/py3/google/auth/credentials.py b/contrib/python/google-auth/py3/google/auth/credentials.py
index 27abd443dc0..e31930311be 100644
--- a/contrib/python/google-auth/py3/google/auth/credentials.py
+++ b/contrib/python/google-auth/py3/google/auth/credentials.py
@@ -22,12 +22,13 @@ import os
from google.auth import _helpers, environment_vars
from google.auth import exceptions
from google.auth import metrics
+from google.auth._credentials_base import _BaseCredentials
from google.auth._refresh_worker import RefreshThreadManager
DEFAULT_UNIVERSE_DOMAIN = "googleapis.com"
-class Credentials(metaclass=abc.ABCMeta):
+class Credentials(_BaseCredentials):
"""Base class for all credentials.
All credentials have a :attr:`token` that is used for authentication and
@@ -47,9 +48,8 @@ class Credentials(metaclass=abc.ABCMeta):
"""
def __init__(self):
- self.token = None
- """str: The bearer token that can be used in HTTP headers to make
- authenticated requests."""
+ super(Credentials, self).__init__()
+
self.expiry = None
"""Optional[datetime]: When the token expires and is no longer valid.
If this is None, the token is assumed to never expire."""
@@ -167,9 +167,7 @@ class Credentials(metaclass=abc.ABCMeta):
token (Optional[str]): If specified, overrides the current access
token.
"""
- headers["authorization"] = "Bearer {}".format(
- _helpers.from_bytes(token or self.token)
- )
+ self._apply(headers, token=token)
"""Trust boundary value will be a cached value from global lookup.
The response of trust boundary will be a list of regions and a hex
diff --git a/contrib/python/google-auth/py3/google/auth/transport/_requests_base.py b/contrib/python/google-auth/py3/google/auth/transport/_requests_base.py
new file mode 100644
index 00000000000..ec718d909a3
--- /dev/null
+++ b/contrib/python/google-auth/py3/google/auth/transport/_requests_base.py
@@ -0,0 +1,52 @@
+# Copyright 2024 Google LLC
+#
+# 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.
+
+"""Transport adapter for Base Requests."""
+
+
+import abc
+
+
+_DEFAULT_TIMEOUT = 120 # in second
+
+
+class _BaseAuthorizedSession(metaclass=abc.ABCMeta):
+ """Base class for a Request Session with credentials. This class is intended to capture
+ the common logic between synchronous and asynchronous request sessions and is not intended to
+ be instantiated directly.
+
+ Args:
+ credentials (google.auth._credentials_base.BaseCredentials): The credentials to
+ add to the request.
+ """
+
+ def __init__(self, credentials):
+ self.credentials = credentials
+
+ @abc.abstractmethod
+ def request(
+ self,
+ method,
+ url,
+ data=None,
+ headers=None,
+ max_allowed_time=None,
+ timeout=_DEFAULT_TIMEOUT,
+ **kwargs
+ ):
+ raise NotImplementedError("Request must be implemented")
+
+ @abc.abstractmethod
+ def close(self):
+ raise NotImplementedError("Close must be implemented")
diff --git a/contrib/python/google-auth/py3/google/auth/transport/requests.py b/contrib/python/google-auth/py3/google/auth/transport/requests.py
index 23a69783dc3..68f67c59bdf 100644
--- a/contrib/python/google-auth/py3/google/auth/transport/requests.py
+++ b/contrib/python/google-auth/py3/google/auth/transport/requests.py
@@ -38,6 +38,7 @@ from google.auth import environment_vars
from google.auth import exceptions
from google.auth import transport
import google.auth.transport._mtls_helper
+from google.auth.transport._requests_base import _BaseAuthorizedSession
from google.oauth2 import service_account
_LOGGER = logging.getLogger(__name__)
@@ -292,7 +293,7 @@ class _MutualTlsOffloadAdapter(requests.adapters.HTTPAdapter):
return super(_MutualTlsOffloadAdapter, self).proxy_manager_for(*args, **kwargs)
-class AuthorizedSession(requests.Session):
+class AuthorizedSession(requests.Session, _BaseAuthorizedSession):
"""A Requests Session class with credentials.
This class is used to perform requests to API endpoints that require
@@ -389,7 +390,7 @@ class AuthorizedSession(requests.Session):
default_host=None,
):
super(AuthorizedSession, self).__init__()
- self.credentials = credentials
+ _BaseAuthorizedSession.__init__(self, credentials)
self._refresh_status_codes = refresh_status_codes
self._max_refresh_attempts = max_refresh_attempts
self._refresh_timeout = refresh_timeout
diff --git a/contrib/python/google-auth/py3/google/auth/version.py b/contrib/python/google-auth/py3/google/auth/version.py
index 51f7f62acd7..c41f8776589 100644
--- a/contrib/python/google-auth/py3/google/auth/version.py
+++ b/contrib/python/google-auth/py3/google/auth/version.py
@@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-__version__ = "2.32.0"
+__version__ = "2.33.0"
diff --git a/contrib/python/google-auth/py3/google/oauth2/_client.py b/contrib/python/google-auth/py3/google/oauth2/_client.py
index bce797b88bb..68e13ddc734 100644
--- a/contrib/python/google-auth/py3/google/oauth2/_client.py
+++ b/contrib/python/google-auth/py3/google/oauth2/_client.py
@@ -183,7 +183,11 @@ def _token_endpoint_request_no_throw(
if headers:
headers_to_use.update(headers)
- def _perform_request():
+ response_data = {}
+ retryable_error = False
+
+ retries = _exponential_backoff.ExponentialBackoff()
+ for _ in retries:
response = request(
method="POST", url=token_uri, headers=headers_to_use, body=body, **kwargs
)
@@ -192,7 +196,7 @@ def _token_endpoint_request_no_throw(
if hasattr(response.data, "decode")
else response.data
)
- response_data = ""
+
try:
# response_body should be a JSON
response_data = json.loads(response_body)
@@ -206,18 +210,8 @@ def _token_endpoint_request_no_throw(
status_code=response.status, response_data=response_data
)
- return False, response_data, retryable_error
-
- request_succeeded, response_data, retryable_error = _perform_request()
-
- if request_succeeded or not retryable_error or not can_retry:
- return request_succeeded, response_data, retryable_error
-
- retries = _exponential_backoff.ExponentialBackoff()
- for _ in retries:
- request_succeeded, response_data, retryable_error = _perform_request()
- if request_succeeded or not retryable_error:
- return request_succeeded, response_data, retryable_error
+ if not can_retry or not retryable_error:
+ return False, response_data, retryable_error
return False, response_data, retryable_error
diff --git a/contrib/python/google-auth/py3/google/oauth2/_client_async.py b/contrib/python/google-auth/py3/google/oauth2/_client_async.py
index 2858d862b0b..8867f0a5274 100644
--- a/contrib/python/google-auth/py3/google/oauth2/_client_async.py
+++ b/contrib/python/google-auth/py3/google/oauth2/_client_async.py
@@ -67,7 +67,11 @@ async def _token_endpoint_request_no_throw(
if access_token:
headers["Authorization"] = "Bearer {}".format(access_token)
- async def _perform_request():
+ response_data = {}
+ retryable_error = False
+
+ retries = _exponential_backoff.ExponentialBackoff()
+ for _ in retries:
response = await request(
method="POST", url=token_uri, headers=headers, body=body
)
@@ -93,18 +97,8 @@ async def _token_endpoint_request_no_throw(
status_code=response.status, response_data=response_data
)
- return False, response_data, retryable_error
-
- request_succeeded, response_data, retryable_error = await _perform_request()
-
- if request_succeeded or not retryable_error or not can_retry:
- return request_succeeded, response_data, retryable_error
-
- retries = _exponential_backoff.ExponentialBackoff()
- for _ in retries:
- request_succeeded, response_data, retryable_error = await _perform_request()
- if request_succeeded or not retryable_error:
- return request_succeeded, response_data, retryable_error
+ if not can_retry or not retryable_error:
+ return False, response_data, retryable_error
return False, response_data, retryable_error
diff --git a/contrib/python/google-auth/py3/tests/compute_engine/test__metadata.py b/contrib/python/google-auth/py3/tests/compute_engine/test__metadata.py
index 35e3c089f90..352342f1509 100644
--- a/contrib/python/google-auth/py3/tests/compute_engine/test__metadata.py
+++ b/contrib/python/google-auth/py3/tests/compute_engine/test__metadata.py
@@ -127,13 +127,15 @@ def test_ping_success_retry(mock_metrics_header_value):
assert request.call_count == 2
-def test_ping_failure_bad_flavor():
[email protected]("time.sleep", return_value=None)
+def test_ping_failure_bad_flavor(mock_sleep):
request = make_request("", headers={_metadata._METADATA_FLAVOR_HEADER: "meep"})
assert not _metadata.ping(request)
-def test_ping_failure_connection_failed():
[email protected]("time.sleep", return_value=None)
+def test_ping_failure_connection_failed(mock_sleep):
request = make_request("")
request.side_effect = exceptions.TransportError()
@@ -196,7 +198,8 @@ def test_get_success_json_content_type_charset():
assert result[key] == value
-def test_get_success_retry():
[email protected]("time.sleep", return_value=None)
+def test_get_success_retry(mock_sleep):
key, value = "foo", "bar"
data = json.dumps({key: value})
@@ -312,7 +315,8 @@ def _test_get_success_custom_root_old_variable():
)
-def test_get_failure():
[email protected]("time.sleep", return_value=None)
+def test_get_failure(mock_sleep):
request = make_request("Metadata error", status=http_client.NOT_FOUND)
with pytest.raises(exceptions.TransportError) as excinfo:
@@ -339,7 +343,8 @@ def test_get_return_none_for_not_found_error():
)
-def test_get_failure_connection_failed():
[email protected]("time.sleep", return_value=None)
+def test_get_failure_connection_failed(mock_sleep):
request = make_request("")
request.side_effect = exceptions.TransportError()
diff --git a/contrib/python/google-auth/py3/tests/oauth2/test__client.py b/contrib/python/google-auth/py3/tests/oauth2/test__client.py
index f9a2d3aff49..8736a4e27be 100644
--- a/contrib/python/google-auth/py3/tests/oauth2/test__client.py
+++ b/contrib/python/google-auth/py3/tests/oauth2/test__client.py
@@ -195,8 +195,8 @@ def test__token_endpoint_request_internal_failure_error():
_client._token_endpoint_request(
request, "http://example.com", {"error_description": "internal_failure"}
)
- # request should be called once and then with 3 retries
- assert request.call_count == 4
+ # request with 2 retries
+ assert request.call_count == 3
request = make_request(
{"error": "internal_failure"}, status=http_client.BAD_REQUEST
@@ -206,8 +206,8 @@ def test__token_endpoint_request_internal_failure_error():
_client._token_endpoint_request(
request, "http://example.com", {"error": "internal_failure"}
)
- # request should be called once and then with 3 retries
- assert request.call_count == 4
+ # request with 2 retries
+ assert request.call_count == 3
def test__token_endpoint_request_internal_failure_and_retry_failure_error():
@@ -626,6 +626,6 @@ def test__token_endpoint_request_no_throw_with_retry(can_retry):
)
if can_retry:
- assert mock_request.call_count == 4
+ assert mock_request.call_count == 3
else:
assert mock_request.call_count == 1
diff --git a/contrib/python/google-auth/py3/tests/test__exponential_backoff.py b/contrib/python/google-auth/py3/tests/test__exponential_backoff.py
index 06a54527e6b..95422502b0d 100644
--- a/contrib/python/google-auth/py3/tests/test__exponential_backoff.py
+++ b/contrib/python/google-auth/py3/tests/test__exponential_backoff.py
@@ -13,8 +13,10 @@
# limitations under the License.
import mock
+import pytest # type: ignore
from google.auth import _exponential_backoff
+from google.auth import exceptions
@mock.patch("time.sleep", return_value=None)
@@ -24,18 +26,31 @@ def test_exponential_backoff(mock_time):
iteration_count = 0
for attempt in eb:
- backoff_interval = mock_time.call_args[0][0]
- jitter = curr_wait * eb._randomization_factor
+ if attempt == 1:
+ assert mock_time.call_count == 0
+ else:
+ backoff_interval = mock_time.call_args[0][0]
+ jitter = curr_wait * eb._randomization_factor
- assert (curr_wait - jitter) <= backoff_interval <= (curr_wait + jitter)
- assert attempt == iteration_count + 1
- assert eb.backoff_count == iteration_count + 1
- assert eb._current_wait_in_seconds == eb._multiplier ** (iteration_count + 1)
+ assert (curr_wait - jitter) <= backoff_interval <= (curr_wait + jitter)
+ assert attempt == iteration_count + 1
+ assert eb.backoff_count == iteration_count + 1
+ assert eb._current_wait_in_seconds == eb._multiplier ** iteration_count
- curr_wait = eb._current_wait_in_seconds
+ curr_wait = eb._current_wait_in_seconds
iteration_count += 1
assert eb.total_attempts == _exponential_backoff._DEFAULT_RETRY_TOTAL_ATTEMPTS
assert eb.backoff_count == _exponential_backoff._DEFAULT_RETRY_TOTAL_ATTEMPTS
assert iteration_count == _exponential_backoff._DEFAULT_RETRY_TOTAL_ATTEMPTS
- assert mock_time.call_count == _exponential_backoff._DEFAULT_RETRY_TOTAL_ATTEMPTS
+ assert (
+ mock_time.call_count == _exponential_backoff._DEFAULT_RETRY_TOTAL_ATTEMPTS - 1
+ )
+
+
+def test_minimum_total_attempts():
+ with pytest.raises(exceptions.InvalidValue):
+ _exponential_backoff.ExponentialBackoff(total_attempts=0)
+ with pytest.raises(exceptions.InvalidValue):
+ _exponential_backoff.ExponentialBackoff(total_attempts=-1)
+ _exponential_backoff.ExponentialBackoff(total_attempts=1)
diff --git a/contrib/python/google-auth/py3/tests/test_credentials_async.py b/contrib/python/google-auth/py3/tests/test_credentials_async.py
new file mode 100644
index 00000000000..51e4f0611c8
--- /dev/null
+++ b/contrib/python/google-auth/py3/tests/test_credentials_async.py
@@ -0,0 +1,136 @@
+# Copyright 2024 Google LLC
+#
+# 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.
+
+import pytest # type: ignore
+
+from google.auth import exceptions
+from google.auth.aio import credentials
+
+
+class CredentialsImpl(credentials.Credentials):
+ pass
+
+
+def test_credentials_constructor():
+ credentials = CredentialsImpl()
+ assert not credentials.token
+
+
+async def test_before_request():
+ credentials = CredentialsImpl()
+ request = "water"
+ headers = {}
+ credentials.token = "orchid"
+
+ # before_request should not affect the value of the token.
+ await credentials.before_request(request, "http://example.com", "GET", headers)
+ assert credentials.token == "orchid"
+ assert headers["authorization"] == "Bearer orchid"
+ assert "x-allowed-locations" not in headers
+
+ request = "earth"
+ headers = {}
+
+ # Second call shouldn't affect token or headers.
+ await credentials.before_request(request, "http://example.com", "GET", headers)
+ assert credentials.token == "orchid"
+ assert headers["authorization"] == "Bearer orchid"
+ assert "x-allowed-locations" not in headers
+
+
+async def test_static_credentials_ctor():
+ static_creds = credentials.StaticCredentials(token="orchid")
+ assert static_creds.token == "orchid"
+
+
+async def test_static_credentials_apply_default():
+ static_creds = credentials.StaticCredentials(token="earth")
+ headers = {}
+
+ await static_creds.apply(headers)
+ assert headers["authorization"] == "Bearer earth"
+
+ await static_creds.apply(headers, token="orchid")
+ assert headers["authorization"] == "Bearer orchid"
+
+
+async def test_static_credentials_before_request():
+ static_creds = credentials.StaticCredentials(token="orchid")
+ request = "water"
+ headers = {}
+
+ # before_request should not affect the value of the token.
+ await static_creds.before_request(request, "http://example.com", "GET", headers)
+ assert static_creds.token == "orchid"
+ assert headers["authorization"] == "Bearer orchid"
+ assert "x-allowed-locations" not in headers
+
+ request = "earth"
+ headers = {}
+
+ # Second call shouldn't affect token or headers.
+ await static_creds.before_request(request, "http://example.com", "GET", headers)
+ assert static_creds.token == "orchid"
+ assert headers["authorization"] == "Bearer orchid"
+ assert "x-allowed-locations" not in headers
+
+
+async def test_static_credentials_refresh():
+ static_creds = credentials.StaticCredentials(token="orchid")
+ request = "earth"
+
+ with pytest.raises(exceptions.InvalidOperation) as exc:
+ await static_creds.refresh(request)
+ assert exc.match("Static credentials cannot be refreshed.")
+
+
+async def test_anonymous_credentials_ctor():
+ anon = credentials.AnonymousCredentials()
+ assert anon.token is None
+
+
+async def test_anonymous_credentials_refresh():
+ anon = credentials.AnonymousCredentials()
+ request = object()
+ with pytest.raises(exceptions.InvalidOperation) as exc:
+ await anon.refresh(request)
+ assert exc.match("Anonymous credentials cannot be refreshed.")
+
+
+async def test_anonymous_credentials_apply_default():
+ anon = credentials.AnonymousCredentials()
+ headers = {}
+ await anon.apply(headers)
+ assert headers == {}
+ with pytest.raises(ValueError):
+ await anon.apply(headers, token="orchid")
+
+
+async def test_anonymous_credentials_before_request():
+ anon = credentials.AnonymousCredentials()
+ request = object()
+ method = "GET"
+ url = "https://example.com/api/endpoint"
+ headers = {}
+ await anon.before_request(request, method, url, headers)
+ assert headers == {}
diff --git a/contrib/python/google-auth/py3/ya.make b/contrib/python/google-auth/py3/ya.make
index 4ea57aefcc9..caefae5db65 100644
--- a/contrib/python/google-auth/py3/ya.make
+++ b/contrib/python/google-auth/py3/ya.make
@@ -2,7 +2,7 @@
PY3_LIBRARY()
-VERSION(2.32.0)
+VERSION(2.33.0)
LICENSE(Apache-2.0)
@@ -28,6 +28,7 @@ PY_SRCS(
google/auth/__init__.py
google/auth/_cloud_sdk.py
google/auth/_credentials_async.py
+ google/auth/_credentials_base.py
google/auth/_default.py
google/auth/_default_async.py
google/auth/_exponential_backoff.py
@@ -36,6 +37,8 @@ PY_SRCS(
google/auth/_oauth2client.py
google/auth/_refresh_worker.py
google/auth/_service_account_info.py
+ google/auth/aio/__init__.py
+ google/auth/aio/credentials.py
google/auth/api_key.py
google/auth/app_engine.py
google/auth/aws.py
@@ -66,6 +69,7 @@ PY_SRCS(
google/auth/transport/_custom_tls_signer.py
google/auth/transport/_http_client.py
google/auth/transport/_mtls_helper.py
+ google/auth/transport/_requests_base.py
google/auth/transport/grpc.py
google/auth/transport/mtls.py
google/auth/transport/requests.py
diff --git a/contrib/python/hypothesis/py3/.dist-info/METADATA b/contrib/python/hypothesis/py3/.dist-info/METADATA
index 557900da698..1e09939db8f 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.8
+Version: 6.110.1
Summary: A library for property-based testing
Home-page: https://hypothesis.works
Author: David R. MacIver and Zac Hatfield-Dodds
@@ -35,59 +35,59 @@ Classifier: Typing :: Typed
Requires-Python: >=3.8
Description-Content-Type: text/x-rst
License-File: LICENSE.txt
-Requires-Dist: attrs >=22.2.0
-Requires-Dist: sortedcontainers <3.0.0,>=2.1.0
-Requires-Dist: exceptiongroup >=1.0.0 ; python_version < "3.11"
+Requires-Dist: attrs>=22.2.0
+Requires-Dist: sortedcontainers<3.0.0,>=2.1.0
+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.65 ; extra == 'all'
-Requires-Dist: django >=3.2 ; extra == 'all'
-Requires-Dist: dpcontracts >=0.4 ; extra == 'all'
-Requires-Dist: hypothesis-crosshair >=0.0.11 ; 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'
-Requires-Dist: pandas >=1.1 ; extra == 'all'
-Requires-Dist: pytest >=4.6 ; extra == 'all'
-Requires-Dist: python-dateutil >=1.4 ; extra == 'all'
-Requires-Dist: pytz >=2014.1 ; extra == 'all'
-Requires-Dist: redis >=3.0.0 ; extra == 'all'
-Requires-Dist: rich >=9.0.0 ; extra == 'all'
-Requires-Dist: backports.zoneinfo >=0.2.1 ; (python_version < "3.9") and extra == 'all'
-Requires-Dist: tzdata >=2024.1 ; (sys_platform == "win32" or sys_platform == "emscripten") and extra == 'all'
+Requires-Dist: black>=19.10b0; extra == "all"
+Requires-Dist: click>=7.0; extra == "all"
+Requires-Dist: crosshair-tool>=0.0.65; extra == "all"
+Requires-Dist: django>=3.2; extra == "all"
+Requires-Dist: dpcontracts>=0.4; extra == "all"
+Requires-Dist: hypothesis-crosshair>=0.0.11; 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"
+Requires-Dist: pandas>=1.1; extra == "all"
+Requires-Dist: pytest>=4.6; extra == "all"
+Requires-Dist: python-dateutil>=1.4; extra == "all"
+Requires-Dist: pytz>=2014.1; extra == "all"
+Requires-Dist: redis>=3.0.0; extra == "all"
+Requires-Dist: rich>=9.0.0; extra == "all"
+Requires-Dist: backports.zoneinfo>=0.2.1; python_version < "3.9" and extra == "all"
+Requires-Dist: tzdata>=2024.1; (sys_platform == "win32" or sys_platform == "emscripten") and extra == "all"
Provides-Extra: cli
-Requires-Dist: click >=7.0 ; extra == 'cli'
-Requires-Dist: black >=19.10b0 ; extra == 'cli'
-Requires-Dist: rich >=9.0.0 ; extra == 'cli'
+Requires-Dist: click>=7.0; extra == "cli"
+Requires-Dist: black>=19.10b0; extra == "cli"
+Requires-Dist: rich>=9.0.0; extra == "cli"
Provides-Extra: codemods
-Requires-Dist: libcst >=0.3.16 ; extra == 'codemods'
+Requires-Dist: libcst>=0.3.16; extra == "codemods"
Provides-Extra: crosshair
-Requires-Dist: hypothesis-crosshair >=0.0.11 ; extra == 'crosshair'
-Requires-Dist: crosshair-tool >=0.0.65 ; extra == 'crosshair'
+Requires-Dist: hypothesis-crosshair>=0.0.11; extra == "crosshair"
+Requires-Dist: crosshair-tool>=0.0.65; extra == "crosshair"
Provides-Extra: dateutil
-Requires-Dist: python-dateutil >=1.4 ; extra == 'dateutil'
+Requires-Dist: python-dateutil>=1.4; extra == "dateutil"
Provides-Extra: django
-Requires-Dist: django >=3.2 ; extra == 'django'
+Requires-Dist: django>=3.2; extra == "django"
Provides-Extra: dpcontracts
-Requires-Dist: dpcontracts >=0.4 ; extra == 'dpcontracts'
+Requires-Dist: dpcontracts>=0.4; extra == "dpcontracts"
Provides-Extra: ghostwriter
-Requires-Dist: black >=19.10b0 ; extra == 'ghostwriter'
+Requires-Dist: black>=19.10b0; extra == "ghostwriter"
Provides-Extra: lark
-Requires-Dist: lark >=0.10.1 ; extra == 'lark'
+Requires-Dist: lark>=0.10.1; extra == "lark"
Provides-Extra: numpy
-Requires-Dist: numpy >=1.17.3 ; extra == 'numpy'
+Requires-Dist: numpy>=1.17.3; extra == "numpy"
Provides-Extra: pandas
-Requires-Dist: pandas >=1.1 ; extra == 'pandas'
+Requires-Dist: pandas>=1.1; extra == "pandas"
Provides-Extra: pytest
-Requires-Dist: pytest >=4.6 ; extra == 'pytest'
+Requires-Dist: pytest>=4.6; extra == "pytest"
Provides-Extra: pytz
-Requires-Dist: pytz >=2014.1 ; extra == 'pytz'
+Requires-Dist: pytz>=2014.1; extra == "pytz"
Provides-Extra: redis
-Requires-Dist: redis >=3.0.0 ; extra == 'redis'
+Requires-Dist: redis>=3.0.0; extra == "redis"
Provides-Extra: zoneinfo
-Requires-Dist: backports.zoneinfo >=0.2.1 ; (python_version < "3.9") and extra == 'zoneinfo'
-Requires-Dist: tzdata >=2024.1 ; (sys_platform == "win32" or sys_platform == "emscripten") and extra == 'zoneinfo'
+Requires-Dist: backports.zoneinfo>=0.2.1; python_version < "3.9" and extra == "zoneinfo"
+Requires-Dist: tzdata>=2024.1; (sys_platform == "win32" or sys_platform == "emscripten") and extra == "zoneinfo"
==========
Hypothesis
diff --git a/contrib/python/hypothesis/py3/hypothesis/extra/django/_impl.py b/contrib/python/hypothesis/py3/hypothesis/extra/django/_impl.py
index d4bcefb0c1d..09a1c2707cd 100644
--- a/contrib/python/hypothesis/py3/hypothesis/extra/django/_impl.py
+++ b/contrib/python/hypothesis/py3/hypothesis/extra/django/_impl.py
@@ -204,7 +204,7 @@ def from_form(
return _forms_impl(
st.builds(
- partial(form, **form_kwargs),
+ partial(form, **form_kwargs), # type: ignore
data=st.fixed_dictionaries(field_strategies), # type: ignore
)
)
diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/cathetus.py b/contrib/python/hypothesis/py3/hypothesis/internal/cathetus.py
index 30e0d214f1c..1f8f2fe82b2 100644
--- a/contrib/python/hypothesis/py3/hypothesis/internal/cathetus.py
+++ b/contrib/python/hypothesis/py3/hypothesis/internal/cathetus.py
@@ -27,7 +27,7 @@ def cathetus(h, a):
may be inaccurate up to a relative error of (around) floating-point
epsilon.
- Based on the C99 implementation https://github.com/jjgreen/cathetus
+ Based on the C99 implementation https://gitlab.com/jjg/cathetus
"""
if isnan(h):
return nan
diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/data.py b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/data.py
index 785eb92328f..40aad2e8501 100644
--- a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/data.py
+++ b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/data.py
@@ -155,9 +155,9 @@ class Status(IntEnum):
@dataclass_transform()
[email protected](frozen=True, slots=True, auto_attribs=True)
[email protected](slots=True, frozen=True)
class StructuralCoverageTag:
- label: int
+ label: int = attr.ib()
STRUCTURAL_COVERAGE_CACHE: Dict[int, StructuralCoverageTag] = {}
@@ -679,6 +679,12 @@ class Examples:
i += n
return Example(self, i)
+ # not strictly necessary as we have len/getitem, but required for mypy.
+ # https://github.com/python/mypy/issues/9737
+ def __iter__(self) -> Iterator[Example]:
+ for i in range(len(self)):
+ yield self[i]
+
@dataclass_transform()
@attr.s(slots=True, frozen=True)
diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/engine.py b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/engine.py
index 71f2ccc0b59..91f731cf44d 100644
--- a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/engine.py
+++ b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/engine.py
@@ -999,7 +999,10 @@ class ConjectureRunner:
self._current_phase = "generate"
prefix = self.generate_novel_prefix()
- assert len(prefix) <= BUFFER_SIZE
+ # it is possible, if unlikely, to generate a > BUFFER_SIZE novel prefix,
+ # as nodes in the novel tree may be variable sized due to eg integer
+ # probe retries.
+ prefix = prefix[:BUFFER_SIZE]
if (
self.valid_examples <= small_example_cap
and self.call_count <= 5 * small_example_cap
diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/junkdrawer.py b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/junkdrawer.py
index 4465f59e5c4..39382637db5 100644
--- a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/junkdrawer.py
+++ b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/junkdrawer.py
@@ -19,12 +19,14 @@ import time
import warnings
from random import Random
from typing import (
+ Any,
Callable,
Dict,
Generic,
Iterable,
Iterator,
List,
+ Literal,
Optional,
Sequence,
Tuple,
@@ -109,10 +111,10 @@ class IntList(Sequence[int]):
def count(self, value: int) -> int:
return self.__underlying.count(value)
- def __repr__(self):
+ def __repr__(self) -> str:
return f"IntList({list(self.__underlying)!r})"
- def __len__(self):
+ def __len__(self) -> int:
return len(self.__underlying)
@overload
@@ -305,7 +307,7 @@ class ensure_free_stackframes:
a reasonable value of N).
"""
- def __enter__(self):
+ def __enter__(self) -> None:
cur_depth = stack_depth_of_caller()
self.old_maxdepth = sys.getrecursionlimit()
# The default CPython recursionlimit is 1000, but pytest seems to bump
@@ -418,8 +420,8 @@ class SelfOrganisingList(Generic[T]):
_gc_initialized = False
-_gc_start = 0
-_gc_cumulative_time = 0
+_gc_start: float = 0
+_gc_cumulative_time: float = 0
# Since gc_callback potentially runs in test context, and perf_counter
# might be monkeypatched, we store a reference to the real one.
@@ -431,7 +433,9 @@ def gc_cumulative_time() -> float:
if not _gc_initialized:
if hasattr(gc, "callbacks"):
# CPython
- def gc_callback(phase, info):
+ def gc_callback(
+ phase: Literal["start", "stop"], info: Dict[str, int]
+ ) -> None:
global _gc_start, _gc_cumulative_time
try:
now = _perf_counter()
@@ -453,7 +457,7 @@ def gc_cumulative_time() -> float:
gc.callbacks.insert(0, gc_callback)
elif hasattr(gc, "hooks"): # pragma: no cover # pypy only
# PyPy
- def hook(stats):
+ def hook(stats: Any) -> None:
global _gc_cumulative_time
try:
_gc_cumulative_time += stats.duration
diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/optimiser.py b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/optimiser.py
index 2f8f7612234..a8f4478453e 100644
--- a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/optimiser.py
+++ b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/optimiser.py
@@ -8,9 +8,11 @@
# 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/.
+from typing import Union
+
from hypothesis.internal.compat import int_from_bytes, int_to_bytes
-from hypothesis.internal.conjecture.data import Status
-from hypothesis.internal.conjecture.engine import BUFFER_SIZE
+from hypothesis.internal.conjecture.data import ConjectureResult, Status, _Overrun
+from hypothesis.internal.conjecture.engine import BUFFER_SIZE, ConjectureRunner
from hypothesis.internal.conjecture.junkdrawer import find_integer
from hypothesis.internal.conjecture.pareto import NO_SCORE
@@ -31,7 +33,13 @@ class Optimiser:
Software Testing and Analysis. ACM, 2017.
"""
- def __init__(self, engine, data, target, max_improvements=100):
+ def __init__(
+ self,
+ engine: ConjectureRunner,
+ data: ConjectureResult,
+ target: str,
+ max_improvements: int = 100,
+ ) -> None:
"""Optimise ``target`` starting from ``data``. Will stop either when
we seem to have found a local maximum or when the target score has
been improved ``max_improvements`` times. This limit is in place to
@@ -42,21 +50,22 @@ class Optimiser:
self.max_improvements = max_improvements
self.improvements = 0
- def run(self):
+ def run(self) -> None:
self.hill_climb()
- def score_function(self, data):
+ def score_function(self, data: ConjectureResult) -> float:
return data.target_observations.get(self.target, NO_SCORE)
@property
- def current_score(self):
+ def current_score(self) -> float:
return self.score_function(self.current_data)
- def consider_new_test_data(self, data):
+ def consider_new_data(self, data: Union[ConjectureResult, _Overrun]) -> bool:
"""Consider a new data object as a candidate target. If it is better
than the current one, return True."""
if data.status < Status.VALID:
return False
+ assert isinstance(data, ConjectureResult)
score = self.score_function(data)
if score < self.current_score:
return False
@@ -73,7 +82,7 @@ class Optimiser:
return True
return False
- def hill_climb(self):
+ def hill_climb(self) -> None:
"""The main hill climbing loop where we actually do the work: Take
data, and attempt to improve its score for target. select_example takes
a data object and returns an index to an example where we should focus
@@ -104,7 +113,7 @@ class Optimiser:
if existing_as_int == max_int_value:
continue
- def attempt_replace(v):
+ def attempt_replace(v: int) -> bool:
"""Try replacing the current block in the current best test case
with an integer of value i. Note that we use the *current*
best and not the one we started with. This helps ensure that
@@ -126,12 +135,14 @@ class Optimiser:
+ bytes(BUFFER_SIZE),
)
- if self.consider_new_test_data(attempt):
+ if self.consider_new_data(attempt):
return True
- if attempt.status < Status.INVALID or len(attempt.buffer) == len(
- self.current_data.buffer
- ):
+ if attempt.status == Status.OVERRUN:
+ return False
+
+ assert isinstance(attempt, ConjectureResult)
+ if len(attempt.buffer) == len(self.current_data.buffer):
return False
for i, ex in enumerate(self.current_data.examples):
@@ -143,7 +154,7 @@ class Optimiser:
if ex.length == ex_attempt.length:
continue # pragma: no cover
replacement = attempt.buffer[ex_attempt.start : ex_attempt.end]
- if self.consider_new_test_data(
+ if self.consider_new_data(
self.engine.cached_test_function(
prefix
+ replacement
diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/shrinker.py b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/shrinker.py
index a0043383720..d4418217876 100644
--- a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/shrinker.py
+++ b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/shrinker.py
@@ -9,7 +9,7 @@
# obtain one at https://mozilla.org/MPL/2.0/.
from collections import defaultdict
-from typing import TYPE_CHECKING, Callable, Dict, Optional, Union
+from typing import TYPE_CHECKING, Callable, Dict, Optional, Tuple, TypeVar, Union
import attr
@@ -38,10 +38,14 @@ from hypothesis.internal.conjecture.shrinking import (
)
if TYPE_CHECKING:
+ from random import Random
+
from hypothesis.internal.conjecture.engine import ConjectureRunner
+SortKeyT = TypeVar("SortKeyT", str, bytes)
+
-def sort_key(buffer):
+def sort_key(buffer: SortKeyT) -> Tuple[int, SortKeyT]:
"""Returns a sort key such that "simpler" buffers are smaller than
"more complicated" ones.
@@ -357,16 +361,16 @@ class Shrinker:
return self.passes_by_name[name]
@property
- def calls(self):
+ def calls(self) -> int:
"""Return the number of calls that have been made to the underlying
test function."""
return self.engine.call_count
@property
- def misaligned(self):
+ def misaligned(self) -> int:
return self.engine.misaligned_count
- def check_calls(self):
+ def check_calls(self) -> None:
if self.calls - self.calls_at_last_shrink >= self.max_stall:
raise StopShrinking
@@ -449,11 +453,11 @@ class Shrinker:
self.check_calls()
return result
- def debug(self, msg):
+ def debug(self, msg: str) -> None:
self.engine.debug(msg)
@property
- def random(self):
+ def random(self) -> "Random":
return self.engine.random
def shrink(self):
@@ -1068,10 +1072,11 @@ class Shrinker:
if node.was_forced:
return False # pragma: no cover
- if node.ir_type == "string":
+ if node.ir_type in {"string", "bytes"}:
+ size_kwarg = "min_size" if node.ir_type == "string" else "size"
# if the size *increased*, we would have to guess what to pad with
# in order to try fixing up this attempt. Just give up.
- if node.kwargs["min_size"] <= attempt_kwargs["min_size"]:
+ if node.kwargs[size_kwarg] <= attempt_kwargs[size_kwarg]:
return False
# the size decreased in our attempt. Try again, but replace with
# the min_size that we would have gotten, and truncate the value
@@ -1082,22 +1087,7 @@ class Shrinker:
initial_attempt[node.index].copy(
with_kwargs=attempt_kwargs,
with_value=initial_attempt[node.index].value[
- : attempt_kwargs["min_size"]
- ],
- )
- ]
- + initial_attempt[node.index :]
- )
- if node.ir_type == "bytes":
- if node.kwargs["size"] <= attempt_kwargs["size"]:
- return False
- return self.consider_new_tree(
- initial_attempt[: node.index]
- + [
- initial_attempt[node.index].copy(
- with_kwargs=attempt_kwargs,
- with_value=initial_attempt[node.index].value[
- : attempt_kwargs["size"]
+ : attempt_kwargs[size_kwarg]
],
)
]
diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/shrinking/collection.py b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/shrinking/collection.py
index bc96e14d484..a87a27b359d 100644
--- a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/shrinking/collection.py
+++ b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/shrinking/collection.py
@@ -10,10 +10,7 @@
from hypothesis.internal.conjecture.shrinking.common import Shrinker
from hypothesis.internal.conjecture.shrinking.ordering import Ordering
-
-
-def identity(v):
- return v
+from hypothesis.internal.conjecture.utils import identity
class Collection(Shrinker):
diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/shrinking/common.py b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/shrinking/common.py
index 1de89bd18b8..b0c5ec8694e 100644
--- a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/shrinking/common.py
+++ b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/shrinking/common.py
@@ -38,10 +38,10 @@ class Shrinker:
self.debugging_enabled = debug
@property
- def calls(self):
+ def calls(self) -> int:
return len(self.__seen)
- def __repr__(self):
+ def __repr__(self) -> str:
return "{}({}initial={!r}, current={!r})".format(
type(self).__name__,
"" if self.name is None else f"{self.name!r}, ",
@@ -75,7 +75,7 @@ class Shrinker:
return other_class.shrink(initial, predicate, **kwargs)
- def debug(self, *args):
+ def debug(self, *args: object) -> None:
if self.debugging_enabled:
print("DEBUG", self, *args)
diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/shrinking/ordering.py b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/shrinking/ordering.py
index f112308f5c2..4e48bdf7b98 100644
--- a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/shrinking/ordering.py
+++ b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/shrinking/ordering.py
@@ -10,10 +10,7 @@
from hypothesis.internal.conjecture.junkdrawer import find_integer
from hypothesis.internal.conjecture.shrinking.common import Shrinker
-
-
-def identity(v):
- return v
+from hypothesis.internal.conjecture.utils import identity
class Ordering(Shrinker):
diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/utils.py b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/utils.py
index 773324bc80b..3724c82177d 100644
--- a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/utils.py
+++ b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/utils.py
@@ -51,6 +51,10 @@ ONE_FROM_MANY_LABEL = calc_label_from_name("one more from many()")
T = TypeVar("T")
+def identity(v: T) -> T:
+ return v
+
+
def check_sample(
values: Union[Type[enum.Enum], Sequence[T]], strategy_name: str
) -> Sequence[T]:
diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/floats.py b/contrib/python/hypothesis/py3/hypothesis/internal/floats.py
index 6c4210e997b..79e6433dca8 100644
--- a/contrib/python/hypothesis/py3/hypothesis/internal/floats.py
+++ b/contrib/python/hypothesis/py3/hypothesis/internal/floats.py
@@ -11,22 +11,50 @@
import math
import struct
from sys import float_info
-from typing import Callable, Optional, SupportsFloat
+from typing import (
+ TYPE_CHECKING,
+ Callable,
+ Dict,
+ Literal,
+ Optional,
+ SupportsFloat,
+ Tuple,
+ Union,
+)
+
+if TYPE_CHECKING:
+ from typing import TypeAlias
+else:
+ TypeAlias = object
+
+SignedIntFormat: "TypeAlias" = Literal["!h", "!i", "!q"]
+UnsignedIntFormat: "TypeAlias" = Literal["!H", "!I", "!Q"]
+IntFormat: "TypeAlias" = Union[SignedIntFormat, UnsignedIntFormat]
+FloatFormat: "TypeAlias" = Literal["!e", "!f", "!d"]
+Width: "TypeAlias" = Literal[16, 32, 64]
# Format codes for (int, float) sized types, used for byte-wise casts.
# See https://docs.python.org/3/library/struct.html#format-characters
-STRUCT_FORMATS = {
+STRUCT_FORMATS: Dict[int, Tuple[UnsignedIntFormat, FloatFormat]] = {
16: ("!H", "!e"),
32: ("!I", "!f"),
64: ("!Q", "!d"),
}
+TO_SIGNED_FORMAT: Dict[UnsignedIntFormat, SignedIntFormat] = {
+ "!H": "!h",
+ "!I": "!i",
+ "!Q": "!q",
+}
+
-def reinterpret_bits(x, from_, to):
- return struct.unpack(to, struct.pack(from_, x))[0]
+def reinterpret_bits(x: float, from_: str, to: str) -> float:
+ x = struct.unpack(to, struct.pack(from_, x))[0]
+ assert isinstance(x, (float, int))
+ return x
-def float_of(x, width):
+def float_of(x: SupportsFloat, width: Width) -> float:
assert width in (16, 32, 64)
if width == 64:
return float(x)
@@ -45,7 +73,7 @@ def is_negative(x: SupportsFloat) -> bool:
) from None
-def count_between_floats(x, y, width=64):
+def count_between_floats(x: float, y: float, width: int = 64) -> int:
assert x <= y
if is_negative(x):
if is_negative(y):
@@ -59,17 +87,19 @@ def count_between_floats(x, y, width=64):
return float_to_int(y, width) - float_to_int(x, width) + 1
-def float_to_int(value, width=64):
+def float_to_int(value: float, width: int = 64) -> int:
fmt_int, fmt_flt = STRUCT_FORMATS[width]
- return reinterpret_bits(value, fmt_flt, fmt_int)
+ x = reinterpret_bits(value, fmt_flt, fmt_int)
+ assert isinstance(x, int)
+ return x
-def int_to_float(value, width=64):
+def int_to_float(value: int, width: int = 64) -> float:
fmt_int, fmt_flt = STRUCT_FORMATS[width]
return reinterpret_bits(value, fmt_int, fmt_flt)
-def next_up(value, width=64):
+def next_up(value: float, width: int = 64) -> float:
"""Return the first float larger than finite `val` - IEEE 754's `nextUp`.
From https://stackoverflow.com/a/10426033, with thanks to Mark Dickinson.
@@ -81,34 +111,34 @@ def next_up(value, width=64):
return 0.0
fmt_int, fmt_flt = STRUCT_FORMATS[width]
# Note: n is signed; float_to_int returns unsigned
- fmt_int = fmt_int.lower()
- n = reinterpret_bits(value, fmt_flt, fmt_int)
+ fmt_int_signed = TO_SIGNED_FORMAT[fmt_int]
+ n = reinterpret_bits(value, fmt_flt, fmt_int_signed)
if n >= 0:
n += 1
else:
n -= 1
- return reinterpret_bits(n, fmt_int, fmt_flt)
+ return reinterpret_bits(n, fmt_int_signed, fmt_flt)
-def next_down(value, width=64):
+def next_down(value: float, width: int = 64) -> float:
return -next_up(-value, width)
-def next_down_normal(value, width, allow_subnormal):
+def next_down_normal(value: float, width: int, *, allow_subnormal: bool) -> float:
value = next_down(value, width)
if (not allow_subnormal) and 0 < abs(value) < width_smallest_normals[width]:
return 0.0 if value > 0 else -width_smallest_normals[width]
return value
-def next_up_normal(value, width, allow_subnormal):
- return -next_down_normal(-value, width, allow_subnormal)
+def next_up_normal(value: float, width: int, *, allow_subnormal: bool) -> float:
+ return -next_down_normal(-value, width, allow_subnormal=allow_subnormal)
# Smallest positive non-zero numbers that is fully representable by an
# IEEE-754 float, calculated with the width's associated minimum exponent.
# Values from https://en.wikipedia.org/wiki/IEEE_754#Basic_and_interchange_formats
-width_smallest_normals = {
+width_smallest_normals: Dict[int, float] = {
16: 2 ** -(2 ** (5 - 1) - 2),
32: 2 ** -(2 ** (8 - 1) - 2),
64: 2 ** -(2 ** (11 - 1) - 2),
diff --git a/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/core.py b/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/core.py
index f7e7dbc2721..7fdeedb4976 100644
--- a/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/core.py
+++ b/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/core.py
@@ -83,7 +83,11 @@ from hypothesis.internal.compat import (
get_type_hints,
is_typed_named_tuple,
)
-from hypothesis.internal.conjecture.utils import calc_label_from_cls, check_sample
+from hypothesis.internal.conjecture.utils import (
+ calc_label_from_cls,
+ check_sample,
+ identity,
+)
from hypothesis.internal.entropy import get_seeder_and_restorer
from hypothesis.internal.floats import float_of
from hypothesis.internal.observability import TESTCASE_CALLBACKS
@@ -271,10 +275,6 @@ def sampled_from(
return SampledFromStrategy(values, repr_)
-def identity(x):
- return x
-
-
@cacheable
@defines_strategy()
def lists(
@@ -1295,6 +1295,12 @@ def _from_type(thing: Type[Ex]) -> SearchStrategy[Ex]:
if types.is_a_union(thing):
args = sorted(thing.__args__, key=types.type_sorting_key)
return one_of([_from_type(t) for t in args])
+ if thing in types.LiteralStringTypes: # pragma: no cover
+ # We can't really cover this because it needs either
+ # typing-extensions or python3.11+ typing.
+ # `LiteralString` from runtime's point of view is just a string.
+ # Fallback to regular text.
+ return text()
# We also have a special case for TypeVars.
# They are represented as instances like `~T` when they come here.
# We need to work with their type instead.
@@ -1340,27 +1346,68 @@ def _from_type(thing: Type[Ex]) -> SearchStrategy[Ex]:
or hasattr(types.typing_extensions, "_TypedDictMeta") # type: ignore
and type(thing) is types.typing_extensions._TypedDictMeta # type: ignore
): # pragma: no cover
+
+ def _get_annotation_arg(key, annotation_type):
+ try:
+ return get_args(annotation_type)[0]
+ except IndexError:
+ raise InvalidArgument(
+ f"`{key}: {annotation_type.__name__}` is not a valid type annotation"
+ ) from None
+
+ # Taken from `Lib/typing.py` and modified:
+ def _get_typeddict_qualifiers(key, annotation_type):
+ qualifiers = []
+ while True:
+ annotation_origin = types.extended_get_origin(annotation_type)
+ if annotation_origin in types.AnnotatedTypes:
+ if annotation_args := get_args(annotation_type):
+ annotation_type = annotation_args[0]
+ else:
+ break
+ elif annotation_origin in types.RequiredTypes:
+ qualifiers.append(types.RequiredTypes)
+ annotation_type = _get_annotation_arg(key, annotation_type)
+ elif annotation_origin in types.NotRequiredTypes:
+ qualifiers.append(types.NotRequiredTypes)
+ annotation_type = _get_annotation_arg(key, annotation_type)
+ elif annotation_origin in types.ReadOnlyTypes:
+ qualifiers.append(types.ReadOnlyTypes)
+ annotation_type = _get_annotation_arg(key, annotation_type)
+ else:
+ break
+ return set(qualifiers), annotation_type
+
# The __optional_keys__ attribute may or may not be present, but if there's no
# way to tell and we just have to assume that everything is required.
# See https://github.com/python/cpython/pull/17214 for details.
optional = set(getattr(thing, "__optional_keys__", ()))
+ required = set(
+ getattr(thing, "__required_keys__", get_type_hints(thing).keys())
+ )
anns = {}
for k, v in get_type_hints(thing).items():
- origin = get_origin(v)
- if origin in types.RequiredTypes + types.NotRequiredTypes:
- if origin in types.NotRequiredTypes:
- optional.add(k)
- else:
- optional.discard(k)
- try:
- v = v.__args__[0]
- except IndexError:
- raise InvalidArgument(
- f"`{k}: {v.__name__}` is not a valid type annotation"
- ) from None
+ qualifiers, v = _get_typeddict_qualifiers(k, v)
+ # We ignore `ReadOnly` type for now, only unwrap it.
+ if types.RequiredTypes in qualifiers:
+ optional.discard(k)
+ required.add(k)
+ if types.NotRequiredTypes in qualifiers:
+ optional.add(k)
+ required.discard(k)
+
anns[k] = from_type_guarded(v)
if anns[k] is ...:
anns[k] = _from_type_deferred(v)
+
+ if not required.isdisjoint(optional): # pragma: no cover
+ # It is impossible to cover, because `typing.py` or `typing-extensions`
+ # won't allow creating incorrect TypedDicts,
+ # this is just a sanity check from our side.
+ raise InvalidArgument(
+ f"Required keys overlap with optional keys in a TypedDict:"
+ f" {required=}, {optional=}"
+ )
if (
(not anns)
and thing.__annotations__
@@ -1368,7 +1415,7 @@ def _from_type(thing: Type[Ex]) -> SearchStrategy[Ex]:
):
raise InvalidArgument("Failed to retrieve type annotations for local type")
return fixed_dictionaries( # type: ignore
- mapping={k: v for k, v in anns.items() if k not in optional},
+ mapping={k: v for k, v in anns.items() if k in required},
optional={k: v for k, v in anns.items() if k in optional},
)
diff --git a/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/numbers.py b/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/numbers.py
index 507cf879d63..033577f2c86 100644
--- a/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/numbers.py
+++ b/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/numbers.py
@@ -380,22 +380,30 @@ def floats(
if min_value is not None and (
exclude_min or (min_arg is not None and min_value < min_arg)
):
- min_value = next_up_normal(min_value, width, assumed_allow_subnormal)
+ min_value = next_up_normal(
+ min_value, width, allow_subnormal=assumed_allow_subnormal
+ )
if min_value == min_arg:
assert min_value == min_arg == 0
assert is_negative(min_arg)
assert not is_negative(min_value)
- min_value = next_up_normal(min_value, width, assumed_allow_subnormal)
+ min_value = next_up_normal(
+ min_value, width, allow_subnormal=assumed_allow_subnormal
+ )
assert min_value > min_arg # type: ignore
if max_value is not None and (
exclude_max or (max_arg is not None and max_value > max_arg)
):
- max_value = next_down_normal(max_value, width, assumed_allow_subnormal)
+ max_value = next_down_normal(
+ max_value, width, allow_subnormal=assumed_allow_subnormal
+ )
if max_value == max_arg:
assert max_value == max_arg == 0
assert is_negative(max_value)
assert not is_negative(max_arg)
- max_value = next_down_normal(max_value, width, assumed_allow_subnormal)
+ max_value = next_down_normal(
+ max_value, width, allow_subnormal=assumed_allow_subnormal
+ )
assert max_value < max_arg # type: ignore
if min_value == -math.inf:
diff --git a/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/types.py b/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/types.py
index 8753bfb7844..85051cfdbe6 100644
--- a/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/types.py
+++ b/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/types.py
@@ -120,7 +120,11 @@ try:
except AttributeError: # pragma: no cover
pass # Is missing for `python<3.10`
try:
- TypeGuardTypes += (typing_extensions.TypeGuard,)
+ TypeGuardTypes += (typing.TypeIs,) # type: ignore
+except AttributeError: # pragma: no cover
+ pass # Is missing for `python<3.13`
+try:
+ TypeGuardTypes += (typing_extensions.TypeGuard, typing_extensions.TypeIs)
except AttributeError: # pragma: no cover
pass # `typing_extensions` might not be installed
@@ -147,6 +151,49 @@ except AttributeError: # pragma: no cover
pass # `typing_extensions` might not be installed
+ReadOnlyTypes: tuple = ()
+try:
+ ReadOnlyTypes += (typing.ReadOnly,) # type: ignore
+except AttributeError: # pragma: no cover
+ pass # Is missing for `python<3.13`
+try:
+ ReadOnlyTypes += (typing_extensions.ReadOnly,)
+except AttributeError: # pragma: no cover
+ pass # `typing_extensions` might not be installed
+
+
+AnnotatedTypes: tuple = ()
+try:
+ AnnotatedTypes += (typing.Annotated,)
+except AttributeError: # pragma: no cover
+ pass # Is missing for `python<3.9`
+try:
+ AnnotatedTypes += (typing_extensions.Annotated,)
+except AttributeError: # pragma: no cover
+ pass # `typing_extensions` might not be installed
+
+
+LiteralStringTypes: tuple = ()
+try:
+ LiteralStringTypes += (typing.LiteralString,) # type: ignore
+except AttributeError: # pragma: no cover
+ pass # Is missing for `python<3.11`
+try:
+ LiteralStringTypes += (typing_extensions.LiteralString,)
+except AttributeError: # pragma: no cover
+ pass # `typing_extensions` might not be installed
+
+
+# We need this function to use `get_origin` on 3.8 for types added later:
+# in typing-extensions, so we prefer this function over regular `get_origin`
+# when unwrapping `TypedDict`'s annotations.
+try:
+ extended_get_origin = typing_extensions.get_origin
+except AttributeError: # pragma: no cover
+ # `typing_extensions` might not be installed, in this case - fallback:
+ extended_get_origin = get_origin # type: ignore
+
+
# We use this variable to be sure that we are working with a type from `typing`:
typing_root_type = (typing._Final, typing._GenericAlias) # type: ignore
@@ -169,10 +216,10 @@ for name in (
"Self",
"Required",
"NotRequired",
+ "ReadOnly",
"Never",
"TypeVarTuple",
"Unpack",
- "LiteralString",
):
try:
NON_RUNTIME_TYPES += (getattr(typing, name),)
@@ -1024,7 +1071,7 @@ def resolve_Callable(thing):
if get_origin(return_type) in TypeGuardTypes:
raise InvalidArgument(
"Hypothesis cannot yet construct a strategy for callables which "
- f"are PEP-647 TypeGuards (got {return_type!r}). "
+ f"are PEP-647 TypeGuards or PEP-742 TypeIs (got {return_type!r}). "
"Consider using an explicit strategy, or opening an issue."
)
diff --git a/contrib/python/hypothesis/py3/hypothesis/version.py b/contrib/python/hypothesis/py3/hypothesis/version.py
index f800a2e0aae..e02ca3db876 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, 8)
+__version_info__ = (6, 110, 1)
__version__ = ".".join(map(str, __version_info__))
diff --git a/contrib/python/hypothesis/py3/ya.make b/contrib/python/hypothesis/py3/ya.make
index 65d6b4d7697..44835a36ca2 100644
--- a/contrib/python/hypothesis/py3/ya.make
+++ b/contrib/python/hypothesis/py3/ya.make
@@ -2,7 +2,7 @@
PY3_LIBRARY()
-VERSION(6.108.8)
+VERSION(6.110.1)
LICENSE(MPL-2.0)
diff --git a/contrib/python/matplotlib/py2/extern/agg24-svn/ya.make b/contrib/python/matplotlib/py2/extern/agg24-svn/ya.make
index b8e286b07c4..c2ae9ed70fb 100644
--- a/contrib/python/matplotlib/py2/extern/agg24-svn/ya.make
+++ b/contrib/python/matplotlib/py2/extern/agg24-svn/ya.make
@@ -1,5 +1,7 @@
LIBRARY()
+VERSION(2.2.5)
+
LICENSE(PSF-2.0)
ADDINCL(
diff --git a/contrib/python/matplotlib/py2/extern/ttconv/ya.make b/contrib/python/matplotlib/py2/extern/ttconv/ya.make
index 847ab2038db..c36ec960b9c 100644
--- a/contrib/python/matplotlib/py2/extern/ttconv/ya.make
+++ b/contrib/python/matplotlib/py2/extern/ttconv/ya.make
@@ -1,5 +1,7 @@
PY23_LIBRARY()
+VERSION(2.2.5)
+
LICENSE(PSF-2.0)
NO_WSHADOW()
diff --git a/contrib/python/matplotlib/py2/matplotlib/tri/ya.make b/contrib/python/matplotlib/py2/matplotlib/tri/ya.make
index ca8a6324716..fb0545dd9f3 100644
--- a/contrib/python/matplotlib/py2/matplotlib/tri/ya.make
+++ b/contrib/python/matplotlib/py2/matplotlib/tri/ya.make
@@ -1,5 +1,7 @@
PY2_LIBRARY()
+VERSION(2.2.5)
+
LICENSE(PSF-2.0)
NO_COMPILER_WARNINGS()
diff --git a/contrib/python/matplotlib/py2/src/ya.make b/contrib/python/matplotlib/py2/src/ya.make
index 544aba39961..486bc04e49b 100644
--- a/contrib/python/matplotlib/py2/src/ya.make
+++ b/contrib/python/matplotlib/py2/src/ya.make
@@ -1,5 +1,7 @@
PY2_LIBRARY()
+VERSION(2.2.5)
+
LICENSE(PSF-2.0)
NO_COMPILER_WARNINGS()
diff --git a/contrib/python/more-itertools/py3/.dist-info/METADATA b/contrib/python/more-itertools/py3/.dist-info/METADATA
index fb41b0cfe69..c346b408809 100644
--- a/contrib/python/more-itertools/py3/.dist-info/METADATA
+++ b/contrib/python/more-itertools/py3/.dist-info/METADATA
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: more-itertools
-Version: 10.3.0
+Version: 10.4.0
Summary: More routines for operating on iterables, beyond itertools
Keywords: itertools,iterator,iteration,filter,peek,peekable,chunk,chunked
Author-email: Erik Rose <[email protected]>
@@ -247,7 +247,7 @@ Blog posts about ``more-itertools``:
* `Yo, I heard you like decorators <https://www.bbayles.com/index/decorator_factory>`__
* `Tour of Python Itertools <https://martinheinz.dev/blog/16>`__ (`Alternate <https://dev.to/martinheinz/tour-of-python-itertools-4122>`__)
-* `Real-World Python More Itertools <https://www.gidware.com/real-world-more-itertools/>`_
+* `Real-World Python More Itertools <https://python.plainenglish.io/real-world-more-itertools-gideons-blog-a3901c607550>`_
Development
diff --git a/contrib/python/more-itertools/py3/README.rst b/contrib/python/more-itertools/py3/README.rst
index 9f3e9c671f1..5be8552b2d5 100644
--- a/contrib/python/more-itertools/py3/README.rst
+++ b/contrib/python/more-itertools/py3/README.rst
@@ -223,7 +223,7 @@ Blog posts about ``more-itertools``:
* `Yo, I heard you like decorators <https://www.bbayles.com/index/decorator_factory>`__
* `Tour of Python Itertools <https://martinheinz.dev/blog/16>`__ (`Alternate <https://dev.to/martinheinz/tour-of-python-itertools-4122>`__)
-* `Real-World Python More Itertools <https://www.gidware.com/real-world-more-itertools/>`_
+* `Real-World Python More Itertools <https://python.plainenglish.io/real-world-more-itertools-gideons-blog-a3901c607550>`_
Development
diff --git a/contrib/python/more-itertools/py3/more_itertools/__init__.py b/contrib/python/more-itertools/py3/more_itertools/__init__.py
index 9c4662fc31a..2e2fcbbe7b3 100644
--- a/contrib/python/more-itertools/py3/more_itertools/__init__.py
+++ b/contrib/python/more-itertools/py3/more_itertools/__init__.py
@@ -3,4 +3,4 @@
from .more import * # noqa
from .recipes import * # noqa
-__version__ = '10.3.0'
+__version__ = '10.4.0'
diff --git a/contrib/python/more-itertools/py3/more_itertools/more.py b/contrib/python/more-itertools/py3/more_itertools/more.py
index 7b481907dae..3bf2c76b765 100644
--- a/contrib/python/more-itertools/py3/more_itertools/more.py
+++ b/contrib/python/more-itertools/py3/more_itertools/more.py
@@ -3,8 +3,9 @@ import warnings
from collections import Counter, defaultdict, deque, abc
from collections.abc import Sequence
+from contextlib import suppress
from functools import cached_property, partial, reduce, wraps
-from heapq import heapify, heapreplace, heappop
+from heapq import heapify, heapreplace
from itertools import (
chain,
combinations,
@@ -21,10 +22,10 @@ from itertools import (
zip_longest,
product,
)
-from math import comb, e, exp, factorial, floor, fsum, log, perm, tau
+from math import comb, e, exp, factorial, floor, fsum, log, log1p, perm, tau
from queue import Empty, Queue
-from random import random, randrange, uniform
-from operator import itemgetter, mul, sub, gt, lt, ge, le
+from random import random, randrange, shuffle, uniform
+from operator import itemgetter, mul, sub, gt, lt, le
from sys import hexversion, maxsize
from time import monotonic
@@ -34,7 +35,6 @@ from .recipes import (
UnequalIterablesError,
consume,
flatten,
- pairwise,
powerset,
take,
unique_everseen,
@@ -473,12 +473,10 @@ def ilen(iterable):
This consumes the iterable, so handle with care.
"""
- # This approach was selected because benchmarks showed it's likely the
- # fastest of the known implementations at the time of writing.
- # See GitHub tracker: #236, #230.
- counter = count()
- deque(zip(iterable, counter), maxlen=0)
- return next(counter)
+ # This is the "most beautiful of the fast variants" of this function.
+ # If you think you can improve on it, please ensure that your version
+ # is both 10x faster and 10x more beautiful.
+ return sum(compress(repeat(1), zip(iterable)))
def iterate(func, start):
@@ -666,9 +664,9 @@ def distinct_permutations(iterable, r=None):
>>> sorted(distinct_permutations([1, 0, 1]))
[(0, 1, 1), (1, 0, 1), (1, 1, 0)]
- Equivalent to ``set(permutations(iterable))``, except duplicates are not
- generated and thrown away. For larger input sequences this is much more
- efficient.
+ Equivalent to yielding from ``set(permutations(iterable))``, except
+ duplicates are not generated and thrown away. For larger input sequences
+ this is much more efficient.
Duplicate permutations arise when there are duplicated elements in the
input iterable. The number of items returned is
@@ -683,6 +681,25 @@ def distinct_permutations(iterable, r=None):
>>> sorted(distinct_permutations(range(3), r=2))
[(0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)]
+ *iterable* need not be sortable, but note that using equal (``x == y``)
+ but non-identical (``id(x) != id(y)``) elements may produce surprising
+ behavior. For example, ``1`` and ``True`` are equal but non-identical:
+
+ >>> list(distinct_permutations([1, True, '3'])) # doctest: +SKIP
+ [
+ (1, True, '3'),
+ (1, '3', True),
+ ('3', 1, True)
+ ]
+ >>> list(distinct_permutations([1, 2, '3'])) # doctest: +SKIP
+ [
+ (1, 2, '3'),
+ (1, '3', 2),
+ (2, 1, '3'),
+ (2, '3', 1),
+ ('3', 1, 2),
+ ('3', 2, 1)
+ ]
"""
# Algorithm: https://w.wiki/Qai
@@ -749,14 +766,44 @@ def distinct_permutations(iterable, r=None):
i += 1
head[i:], tail[:] = tail[: r - i], tail[r - i :]
- items = sorted(iterable)
+ items = list(iterable)
+
+ try:
+ items.sort()
+ sortable = True
+ except TypeError:
+ sortable = False
+
+ indices_dict = defaultdict(list)
+
+ for item in items:
+ indices_dict[items.index(item)].append(item)
+
+ indices = [items.index(item) for item in items]
+ indices.sort()
+
+ equivalent_items = {k: cycle(v) for k, v in indices_dict.items()}
+
+ def permuted_items(permuted_indices):
+ return tuple(
+ next(equivalent_items[index]) for index in permuted_indices
+ )
size = len(items)
if r is None:
r = size
+ # functools.partial(_partial, ... )
+ algorithm = _full if (r == size) else partial(_partial, r=r)
+
if 0 < r <= size:
- return _full(items) if (r == size) else _partial(items, r)
+ if sortable:
+ return algorithm(items)
+ else:
+ return (
+ permuted_items(permuted_indices)
+ for permuted_indices in algorithm(indices)
+ )
return iter(() if r else ((),))
@@ -1743,7 +1790,9 @@ def zip_offset(*iterables, offsets, longest=False, fillvalue=None):
return zip(*staggered)
-def sort_together(iterables, key_list=(0,), key=None, reverse=False):
+def sort_together(
+ iterables, key_list=(0,), key=None, reverse=False, strict=False
+):
"""Return the input iterables sorted together, with *key_list* as the
priority for sorting. All iterables are trimmed to the length of the
shortest one.
@@ -1782,6 +1831,10 @@ def sort_together(iterables, key_list=(0,), key=None, reverse=False):
>>> sort_together([(1, 2, 3), ('c', 'b', 'a')], reverse=True)
[(3, 2, 1), ('a', 'b', 'c')]
+ If the *strict* keyword argument is ``True``, then
+ ``UnequalIterablesError`` will be raised if any of the iterables have
+ different lengths.
+
"""
if key is None:
# if there is no key function, the key argument to sorted is an
@@ -1804,8 +1857,9 @@ def sort_together(iterables, key_list=(0,), key=None, reverse=False):
*get_key_items(zipped_items)
)
+ zipper = zip_equal if strict else zip
return list(
- zip(*sorted(zip(*iterables), key=key_argument, reverse=reverse))
+ zipper(*sorted(zipper(*iterables), key=key_argument, reverse=reverse))
)
@@ -2747,8 +2801,6 @@ class seekable:
>>> it.seek(0)
>>> next(it), next(it), next(it)
('0', '1', '2')
- >>> next(it)
- '3'
You can also seek forward:
@@ -2756,15 +2808,29 @@ class seekable:
>>> it.seek(10)
>>> next(it)
'10'
- >>> it.relative_seek(-2) # Seeking relative to the current position
- >>> next(it)
- '9'
>>> it.seek(20) # Seeking past the end of the source isn't a problem
>>> list(it)
[]
>>> it.seek(0) # Resetting works even after hitting the end
+ >>> next(it)
+ '0'
+
+ Call :meth:`relative_seek` to seek relative to the source iterator's
+ current position.
+
+ >>> it = seekable((str(n) for n in range(20)))
>>> next(it), next(it), next(it)
('0', '1', '2')
+ >>> it.relative_seek(2)
+ >>> next(it)
+ '5'
+ >>> it.relative_seek(-3) # Source is at '6', we move back to '3'
+ >>> next(it)
+ '3'
+ >>> it.relative_seek(-3) # Source is at '4', we move back to '1'
+ >>> next(it)
+ '1'
+
Call :meth:`peek` to look ahead one item without advancing the iterator:
@@ -2873,8 +2939,10 @@ class seekable:
consume(self, remainder)
def relative_seek(self, count):
- index = len(self._cache)
- self.seek(max(index + count, 0))
+ if self._index is None:
+ self._index = len(self._cache)
+
+ self.seek(max(self._index + count, 0))
class run_length:
@@ -2903,7 +2971,7 @@ class run_length:
@staticmethod
def decode(iterable):
- return chain.from_iterable(repeat(k, n) for k, n in iterable)
+ return chain.from_iterable(starmap(repeat, iterable))
def exactly_n(iterable, n, predicate=bool):
@@ -2924,14 +2992,34 @@ def exactly_n(iterable, n, predicate=bool):
return len(take(n + 1, filter(predicate, iterable))) == n
-def circular_shifts(iterable):
- """Return a list of circular shifts of *iterable*.
+def circular_shifts(iterable, steps=1):
+ """Yield the circular shifts of *iterable*.
- >>> circular_shifts(range(4))
+ >>> list(circular_shifts(range(4)))
[(0, 1, 2, 3), (1, 2, 3, 0), (2, 3, 0, 1), (3, 0, 1, 2)]
+
+ Set *steps* to the number of places to rotate to the left
+ (or to the right if negative). Defaults to 1.
+
+ >>> list(circular_shifts(range(4), 2))
+ [(0, 1, 2, 3), (2, 3, 0, 1)]
+
+ >>> list(circular_shifts(range(4), -1))
+ [(0, 1, 2, 3), (3, 0, 1, 2), (2, 3, 0, 1), (1, 2, 3, 0)]
+
"""
- lst = list(iterable)
- return take(len(lst), windowed(cycle(lst), len(lst)))
+ buffer = deque(iterable)
+ if steps == 0:
+ raise ValueError('Steps should be a non-zero integer')
+
+ buffer.rotate(steps)
+ steps = -steps
+ n = len(buffer)
+ n //= math.gcd(n, steps)
+
+ for __ in repeat(None, n):
+ buffer.rotate(steps)
+ yield tuple(buffer)
def make_decorator(wrapping_func, result_index=0):
@@ -3191,7 +3279,7 @@ def partitions(iterable):
yield [sequence[i:j] for i, j in zip((0,) + i, i + (n,))]
-def set_partitions(iterable, k=None):
+def set_partitions(iterable, k=None, min_size=None, max_size=None):
"""
Yield the set partitions of *iterable* into *k* parts. Set partitions are
not order-preserving.
@@ -3215,6 +3303,20 @@ def set_partitions(iterable, k=None):
['b', 'ac']
['a', 'b', 'c']
+ if *min_size* and/or *max_size* are given, the minimum and/or maximum size
+ per block in partition is set.
+
+ >>> iterable = 'abc'
+ >>> for part in set_partitions(iterable, min_size=2):
+ ... print([''.join(p) for p in part])
+ ['abc']
+ >>> for part in set_partitions(iterable, max_size=2):
+ ... print([''.join(p) for p in part])
+ ['a', 'bc']
+ ['ab', 'c']
+ ['b', 'ac']
+ ['a', 'b', 'c']
+
"""
L = list(iterable)
n = len(L)
@@ -3226,6 +3328,11 @@ def set_partitions(iterable, k=None):
elif k > n:
return
+ min_size = min_size if min_size is not None else 0
+ max_size = max_size if max_size is not None else n
+ if min_size > max_size:
+ return
+
def set_partitions_helper(L, k):
n = len(L)
if k == 1:
@@ -3242,9 +3349,15 @@ def set_partitions(iterable, k=None):
if k is None:
for k in range(1, n + 1):
- yield from set_partitions_helper(L, k)
+ yield from filter(
+ lambda z: all(min_size <= len(bk) <= max_size for bk in z),
+ set_partitions_helper(L, k),
+ )
else:
- yield from set_partitions_helper(L, k)
+ yield from filter(
+ lambda z: all(min_size <= len(bk) <= max_size for bk in z),
+ set_partitions_helper(L, k),
+ )
class time_limited:
@@ -3535,32 +3648,27 @@ def map_if(iterable, pred, func, func_else=lambda x: x):
yield func(item) if pred(item) else func_else(item)
-def _sample_unweighted(iterable, k):
- # Implementation of "Algorithm L" from the 1994 paper by Kim-Hung Li:
+def _sample_unweighted(iterator, k, strict):
+ # Algorithm L in the 1994 paper by Kim-Hung Li:
# "Reservoir-Sampling Algorithms of Time Complexity O(n(1+log(N/n)))".
- # Fill up the reservoir (collection of samples) with the first `k` samples
- reservoir = take(k, iterable)
-
- # Generate random number that's the largest in a sample of k U(0,1) numbers
- # Largest order statistic: https://en.wikipedia.org/wiki/Order_statistic
- W = exp(log(random()) / k)
+ reservoir = list(islice(iterator, k))
+ if strict and len(reservoir) < k:
+ raise ValueError('Sample larger than population')
+ W = 1.0
- # The number of elements to skip before changing the reservoir is a random
- # number with a geometric distribution. Sample it using random() and logs.
- next_index = k + floor(log(random()) / log(1 - W))
-
- for index, element in enumerate(iterable, k):
- if index == next_index:
- reservoir[randrange(k)] = element
- # The new W is the largest in a sample of k U(0, `old_W`) numbers
+ with suppress(StopIteration):
+ while True:
W *= exp(log(random()) / k)
- next_index += floor(log(random()) / log(1 - W)) + 1
+ skip = floor(log(random()) / log1p(-W))
+ element = next(islice(iterator, skip, None))
+ reservoir[randrange(k)] = element
+ shuffle(reservoir)
return reservoir
-def _sample_weighted(iterable, k, weights):
+def _sample_weighted(iterator, k, weights, strict):
# Implementation of "A-ExpJ" from the 2006 paper by Efraimidis et al. :
# "Weighted random sampling with a reservoir".
@@ -3569,7 +3677,10 @@ def _sample_weighted(iterable, k, weights):
# Fill up the reservoir (collection of samples) with the first `k`
# weight-keys and elements, then heapify the list.
- reservoir = take(k, zip(weight_keys, iterable))
+ reservoir = take(k, zip(weight_keys, iterator))
+ if strict and len(reservoir) < k:
+ raise ValueError('Sample larger than population')
+
heapify(reservoir)
# The number of jumps before changing the reservoir is a random variable
@@ -3577,7 +3688,7 @@ def _sample_weighted(iterable, k, weights):
smallest_weight_key, _ = reservoir[0]
weights_to_skip = log(random()) / smallest_weight_key
- for weight, element in zip(weights, iterable):
+ for weight, element in zip(weights, iterator):
if weight >= weights_to_skip:
# The notation here is consistent with the paper, but we store
# the weight-keys in log-space for better numerical stability.
@@ -3591,44 +3702,103 @@ def _sample_weighted(iterable, k, weights):
else:
weights_to_skip -= weight
- # Equivalent to [element for weight_key, element in sorted(reservoir)]
- return [heappop(reservoir)[1] for _ in range(k)]
+ ret = [element for weight_key, element in reservoir]
+ shuffle(ret)
+ return ret
+
+def _sample_counted(population, k, counts, strict):
+ element = None
+ remaining = 0
-def sample(iterable, k, weights=None):
+ def feed(i):
+ # Advance *i* steps ahead and consume an element
+ nonlocal element, remaining
+
+ while i + 1 > remaining:
+ i = i - remaining
+ element = next(population)
+ remaining = next(counts)
+ remaining -= i + 1
+ return element
+
+ with suppress(StopIteration):
+ reservoir = []
+ for _ in range(k):
+ reservoir.append(feed(0))
+ if strict and len(reservoir) < k:
+ raise ValueError('Sample larger than population')
+
+ W = 1.0
+ while True:
+ W *= exp(log(random()) / k)
+ skip = floor(log(random()) / log1p(-W))
+ element = feed(skip)
+ reservoir[randrange(k)] = element
+
+ shuffle(reservoir)
+ return reservoir
+
+
+def sample(iterable, k, weights=None, *, counts=None, strict=False):
"""Return a *k*-length list of elements chosen (without replacement)
- from the *iterable*. Like :func:`random.sample`, but works on iterables
- of unknown length.
+ from the *iterable*. Similar to :func:`random.sample`, but works on
+ iterables of unknown length.
>>> iterable = range(100)
>>> sample(iterable, 5) # doctest: +SKIP
[81, 60, 96, 16, 4]
- An iterable with *weights* may also be given:
+ For iterables with repeated elements, you may supply *counts* to
+ indicate the repeats.
+
+ >>> iterable = ['a', 'b']
+ >>> counts = [3, 4] # Equivalent to 'a', 'a', 'a', 'b', 'b', 'b', 'b'
+ >>> sample(iterable, k=3, counts=counts) # doctest: +SKIP
+ ['a', 'a', 'b']
+
+ An iterable with *weights* may be given:
>>> iterable = range(100)
>>> weights = (i * i + 1 for i in range(100))
>>> sampled = sample(iterable, 5, weights=weights) # doctest: +SKIP
[79, 67, 74, 66, 78]
- The algorithm can also be used to generate weighted random permutations.
- The relative weight of each item determines the probability that it
- appears late in the permutation.
+ Weighted selections are made without replacement.
+ After an element is selected, it is removed from the pool and the
+ relative weights of the other elements increase (this
+ does not match the behavior of :func:`random.sample`'s *counts*
+ parameter). Note that *weights* may not be used with *counts*.
+
+ If the length of *iterable* is less than *k*,
+ ``ValueError`` is raised if *strict* is ``True`` and
+ all elements are returned (in shuffled order) if *strict* is ``False``.
- >>> data = "abcdefgh"
- >>> weights = range(1, len(data) + 1)
- >>> sample(data, k=len(data), weights=weights) # doctest: +SKIP
- ['c', 'a', 'b', 'e', 'g', 'd', 'h', 'f']
+ By default, the `Algorithm L <https://w.wiki/ANrM>`__ reservoir sampling
+ technique is used. When *weights* are provided,
+ `Algorithm A-ExpJ <https://w.wiki/ANrS>`__ is used.
"""
+ iterator = iter(iterable)
+
+ if k < 0:
+ raise ValueError('k must be non-negative')
+
if k == 0:
return []
- iterable = iter(iterable)
- if weights is None:
- return _sample_unweighted(iterable, k)
- else:
+ if weights is not None and counts is not None:
+ raise TypeError('weights and counts are mutally exclusive')
+
+ elif weights is not None:
weights = iter(weights)
- return _sample_weighted(iterable, k, weights)
+ return _sample_weighted(iterator, k, weights, strict)
+
+ elif counts is not None:
+ counts = iter(counts)
+ return _sample_counted(iterator, k, counts, strict)
+
+ else:
+ return _sample_unweighted(iterator, k, strict)
def is_sorted(iterable, key=None, reverse=False, strict=False):
@@ -3650,12 +3820,16 @@ def is_sorted(iterable, key=None, reverse=False, strict=False):
False
The function returns ``False`` after encountering the first out-of-order
- item. If there are no out-of-order items, the iterable is exhausted.
+ item, which means it may produce results that differ from the built-in
+ :func:`sorted` function for objects with unusual comparison dynamics.
+ If there are no out-of-order items, the iterable is exhausted.
"""
+ compare = le if strict else lt
+ it = iterable if (key is None) else map(key, iterable)
+ it_1, it_2 = tee(it)
+ next(it_2 if reverse else it_1, None)
- compare = (le if reverse else ge) if strict else (lt if reverse else gt)
- it = iterable if key is None else map(key, iterable)
- return not any(starmap(compare, pairwise(it)))
+ return not any(map(compare, it_1, it_2))
class AbortThread(BaseException):
diff --git a/contrib/python/more-itertools/py3/more_itertools/more.pyi b/contrib/python/more-itertools/py3/more_itertools/more.pyi
index e9460232593..f1a155dce7d 100644
--- a/contrib/python/more-itertools/py3/more_itertools/more.pyi
+++ b/contrib/python/more-itertools/py3/more_itertools/more.pyi
@@ -2,6 +2,8 @@
from __future__ import annotations
+import sys
+
from types import TracebackType
from typing import (
Any,
@@ -28,6 +30,9 @@ from typing_extensions import Protocol
_T = TypeVar('_T')
_T1 = TypeVar('_T1')
_T2 = TypeVar('_T2')
+_T3 = TypeVar('_T3')
+_T4 = TypeVar('_T4')
+_T5 = TypeVar('_T5')
_U = TypeVar('_U')
_V = TypeVar('_V')
_W = TypeVar('_W')
@@ -35,6 +40,12 @@ _T_co = TypeVar('_T_co', covariant=True)
_GenFn = TypeVar('_GenFn', bound=Callable[..., Iterator[Any]])
_Raisable = BaseException | Type[BaseException]
+# The type of isinstance's second argument (from typeshed builtins)
+if sys.version_info >= (3, 10):
+ _ClassInfo = type | UnionType | tuple[_ClassInfo, ...]
+else:
+ _ClassInfo = type | tuple[_ClassInfo, ...]
+
@type_check_only
class _SizedIterable(Protocol[_T_co], Sized, Iterable[_T_co]): ...
@@ -135,7 +146,7 @@ def interleave_evenly(
) -> Iterator[_T]: ...
def collapse(
iterable: Iterable[Any],
- base_type: type | None = ...,
+ base_type: _ClassInfo | None = ...,
levels: int | None = ...,
) -> Iterator[Any]: ...
@overload
@@ -213,6 +224,7 @@ def stagger(
class UnequalIterablesError(ValueError):
def __init__(self, details: tuple[int, int, int] | None = ...) -> None: ...
+# zip_equal
@overload
def zip_equal(__iter1: Iterable[_T1]) -> Iterator[tuple[_T1]]: ...
@overload
@@ -221,11 +233,35 @@ def zip_equal(
) -> Iterator[tuple[_T1, _T2]]: ...
@overload
def zip_equal(
- __iter1: Iterable[_T],
- __iter2: Iterable[_T],
- __iter3: Iterable[_T],
- *iterables: Iterable[_T],
-) -> Iterator[tuple[_T, ...]]: ...
+ __iter1: Iterable[_T1], __iter2: Iterable[_T2], __iter3: Iterable[_T3]
+) -> Iterator[tuple[_T1, _T2, _T3]]: ...
+@overload
+def zip_equal(
+ __iter1: Iterable[_T1],
+ __iter2: Iterable[_T2],
+ __iter3: Iterable[_T3],
+ __iter4: Iterable[_T4],
+) -> Iterator[tuple[_T1, _T2, _T3, _T4]]: ...
+@overload
+def zip_equal(
+ __iter1: Iterable[_T1],
+ __iter2: Iterable[_T2],
+ __iter3: Iterable[_T3],
+ __iter4: Iterable[_T4],
+ __iter5: Iterable[_T5],
+) -> Iterator[tuple[_T1, _T2, _T3, _T4, _T5]]: ...
+@overload
+def zip_equal(
+ __iter1: Iterable[Any],
+ __iter2: Iterable[Any],
+ __iter3: Iterable[Any],
+ __iter4: Iterable[Any],
+ __iter5: Iterable[Any],
+ __iter6: Iterable[Any],
+ *iterables: Iterable[Any],
+) -> Iterator[tuple[Any, ...]]: ...
+
+# zip_offset
@overload
def zip_offset(
__iter1: Iterable[_T1],
@@ -285,12 +321,13 @@ def sort_together(
key_list: Iterable[int] = ...,
key: Callable[..., Any] | None = ...,
reverse: bool = ...,
+ strict: bool = ...,
) -> list[tuple[_T, ...]]: ...
def unzip(iterable: Iterable[Sequence[_T]]) -> tuple[Iterator[_T], ...]: ...
def divide(n: int, iterable: Iterable[_T]) -> list[Iterator[_T]]: ...
def always_iterable(
obj: object,
- base_type: type | tuple[type | tuple[Any, ...], ...] | None = ...,
+ base_type: _ClassInfo | None = ...,
) -> Iterator[Any]: ...
def adjacent(
predicate: Callable[[_T], bool],
@@ -454,7 +491,9 @@ class run_length:
def exactly_n(
iterable: Iterable[_T], n: int, predicate: Callable[[_T], object] = ...
) -> bool: ...
-def circular_shifts(iterable: Iterable[_T]) -> list[tuple[_T, ...]]: ...
+def circular_shifts(
+ iterable: Iterable[_T], steps: int = 1
+) -> list[tuple[_T, ...]]: ...
def make_decorator(
wrapping_func: Callable[..., _U], result_index: int = ...
) -> Callable[..., Callable[[Callable[..., Any]], Callable[..., _U]]]: ...
@@ -500,7 +539,10 @@ def replace(
) -> Iterator[_T | _U]: ...
def partitions(iterable: Iterable[_T]) -> Iterator[list[list[_T]]]: ...
def set_partitions(
- iterable: Iterable[_T], k: int | None = ...
+ iterable: Iterable[_T],
+ k: int | None = ...,
+ min_size: int | None = ...,
+ max_size: int | None = ...,
) -> Iterator[list[list[_T]]]: ...
class time_limited(Generic[_T], Iterator[_T]):
@@ -538,10 +580,22 @@ def map_if(
func: Callable[[Any], Any],
func_else: Callable[[Any], Any] | None = ...,
) -> Iterator[Any]: ...
+def _sample_unweighted(
+ iterator: Iterator[_T], k: int, strict: bool
+) -> list[_T]: ...
+def _sample_counted(
+ population: Iterator[_T], k: int, counts: Iterable[int], strict: bool
+) -> list[_T]: ...
+def _sample_weighted(
+ iterator: Iterator[_T], k: int, weights, strict
+) -> list[_T]: ...
def sample(
iterable: Iterable[_T],
k: int,
weights: Iterable[float] | None = ...,
+ *,
+ counts: Iterable[int] | None = ...,
+ strict: bool = False,
) -> list[_T]: ...
def is_sorted(
iterable: Iterable[_T],
@@ -577,7 +631,7 @@ class callback_iter(Generic[_T], Iterator[_T]):
def windowed_complete(
iterable: Iterable[_T], n: int
-) -> Iterator[tuple[_T, ...]]: ...
+) -> Iterator[tuple[tuple[_T, ...], tuple[_T, ...], tuple[_T, ...]]]: ...
def all_unique(
iterable: Iterable[_T], key: Callable[[_T], _U] | None = ...
) -> bool: ...
@@ -608,9 +662,61 @@ class countable(Generic[_T], Iterator[_T]):
items_seen: int
def chunked_even(iterable: Iterable[_T], n: int) -> Iterator[list[_T]]: ...
+@overload
+def zip_broadcast(
+ __obj1: _T | Iterable[_T],
+ *,
+ scalar_types: _ClassInfo | None = ...,
+ strict: bool = ...,
+) -> Iterable[tuple[_T, ...]]: ...
+@overload
+def zip_broadcast(
+ __obj1: _T | Iterable[_T],
+ __obj2: _T | Iterable[_T],
+ *,
+ scalar_types: _ClassInfo | None = ...,
+ strict: bool = ...,
+) -> Iterable[tuple[_T, ...]]: ...
+@overload
+def zip_broadcast(
+ __obj1: _T | Iterable[_T],
+ __obj2: _T | Iterable[_T],
+ __obj3: _T | Iterable[_T],
+ *,
+ scalar_types: _ClassInfo | None = ...,
+ strict: bool = ...,
+) -> Iterable[tuple[_T, ...]]: ...
+@overload
+def zip_broadcast(
+ __obj1: _T | Iterable[_T],
+ __obj2: _T | Iterable[_T],
+ __obj3: _T | Iterable[_T],
+ __obj4: _T | Iterable[_T],
+ *,
+ scalar_types: _ClassInfo | None = ...,
+ strict: bool = ...,
+) -> Iterable[tuple[_T, ...]]: ...
+@overload
+def zip_broadcast(
+ __obj1: _T | Iterable[_T],
+ __obj2: _T | Iterable[_T],
+ __obj3: _T | Iterable[_T],
+ __obj4: _T | Iterable[_T],
+ __obj5: _T | Iterable[_T],
+ *,
+ scalar_types: _ClassInfo | None = ...,
+ strict: bool = ...,
+) -> Iterable[tuple[_T, ...]]: ...
+@overload
def zip_broadcast(
+ __obj1: _T | Iterable[_T],
+ __obj2: _T | Iterable[_T],
+ __obj3: _T | Iterable[_T],
+ __obj4: _T | Iterable[_T],
+ __obj5: _T | Iterable[_T],
+ __obj6: _T | Iterable[_T],
*objects: _T | Iterable[_T],
- scalar_types: type | tuple[type | tuple[Any, ...], ...] | None = ...,
+ scalar_types: _ClassInfo | None = ...,
strict: bool = ...,
) -> Iterable[tuple[_T, ...]]: ...
def unique_in_window(
diff --git a/contrib/python/more-itertools/py3/more_itertools/recipes.py b/contrib/python/more-itertools/py3/more_itertools/recipes.py
index b32fa955339..a21a1f5d88d 100644
--- a/contrib/python/more-itertools/py3/more_itertools/recipes.py
+++ b/contrib/python/more-itertools/py3/more_itertools/recipes.py
@@ -795,8 +795,30 @@ def triplewise(iterable):
[('A', 'B', 'C'), ('B', 'C', 'D'), ('C', 'D', 'E')]
"""
- for (a, _), (b, c) in pairwise(pairwise(iterable)):
- yield a, b, c
+ # This deviates from the itertools documentation reciple - see
+ # https://github.com/more-itertools/more-itertools/issues/889
+ t1, t2, t3 = tee(iterable, 3)
+ next(t3, None)
+ next(t3, None)
+ next(t2, None)
+ return zip(t1, t2, t3)
+
+
+def _sliding_window_islice(iterable, n):
+ # Fast path for small, non-zero values of n.
+ iterators = tee(iterable, n)
+ for i, iterator in enumerate(iterators):
+ next(islice(iterator, i, i), None)
+ return zip(*iterators)
+
+
+def _sliding_window_deque(iterable, n):
+ # Normal path for other values of n.
+ it = iter(iterable)
+ window = deque(islice(it, n - 1), maxlen=n)
+ for x in it:
+ window.append(x)
+ yield tuple(window)
def sliding_window(iterable, n):
@@ -812,11 +834,16 @@ def sliding_window(iterable, n):
For a variant with more features, see :func:`windowed`.
"""
- it = iter(iterable)
- window = deque(islice(it, n - 1), maxlen=n)
- for x in it:
- window.append(x)
- yield tuple(window)
+ if n > 20:
+ return _sliding_window_deque(iterable, n)
+ elif n > 2:
+ return _sliding_window_islice(iterable, n)
+ elif n == 2:
+ return pairwise(iterable)
+ elif n == 1:
+ return zip(iterable)
+ else:
+ raise ValueError(f'n should be at least one, not {n}')
def subslices(iterable):
@@ -1038,9 +1065,6 @@ def totient(n):
>>> totient(12)
4
"""
- # The itertools docs use unique_justseen instead of set; see
- # https://github.com/more-itertools/more-itertools/issues/823
- for p in set(factor(n)):
- n = n // p * (p - 1)
-
+ for prime in set(factor(n)):
+ n -= n // prime
return n
diff --git a/contrib/python/more-itertools/py3/tests/test_more.py b/contrib/python/more-itertools/py3/tests/test_more.py
index fda4c0984a3..1a70ea08e57 100644
--- a/contrib/python/more-itertools/py3/tests/test_more.py
+++ b/contrib/python/more-itertools/py3/tests/test_more.py
@@ -473,36 +473,11 @@ class ConsumerTests(TestCase):
class DistinctPermutationsTests(TestCase):
- def test_distinct_permutations(self):
- """Make sure the output for ``distinct_permutations()`` is the same as
- set(permutations(it)).
-
- """
+ def test_basic(self):
iterable = ['z', 'a', 'a', 'q', 'q', 'q', 'y']
- test_output = sorted(mi.distinct_permutations(iterable))
- ref_output = sorted(set(permutations(iterable)))
- self.assertEqual(test_output, ref_output)
-
- def test_other_iterables(self):
- """Make sure ``distinct_permutations()`` accepts a different type of
- iterables.
-
- """
- # a generator
- iterable = (c for c in ['z', 'a', 'a', 'q', 'q', 'q', 'y'])
- test_output = sorted(mi.distinct_permutations(iterable))
- # "reload" it
- iterable = (c for c in ['z', 'a', 'a', 'q', 'q', 'q', 'y'])
- ref_output = sorted(set(permutations(iterable)))
- self.assertEqual(test_output, ref_output)
-
- # an iterator
- iterable = iter(['z', 'a', 'a', 'q', 'q', 'q', 'y'])
- test_output = sorted(mi.distinct_permutations(iterable))
- # "reload" it
- iterable = iter(['z', 'a', 'a', 'q', 'q', 'q', 'y'])
- ref_output = sorted(set(permutations(iterable)))
- self.assertEqual(test_output, ref_output)
+ actual = list(mi.distinct_permutations(iterable))
+ expected = set(permutations(iterable))
+ self.assertCountEqual(actual, expected)
def test_r(self):
for iterable, r in (
@@ -524,9 +499,35 @@ class DistinctPermutationsTests(TestCase):
([], 4),
):
with self.subTest(iterable=iterable, r=r):
- expected = sorted(set(permutations(iterable, r)))
- actual = sorted(mi.distinct_permutations(iter(iterable), r))
- self.assertEqual(actual, expected)
+ expected = set(permutations(iterable, r))
+ actual = list(mi.distinct_permutations(iter(iterable), r))
+ self.assertCountEqual(actual, expected)
+
+ def test_unsortable(self):
+ iterable = ['1', 2, 2, 3, 3, 3]
+ actual = list(mi.distinct_permutations(iterable))
+ expected = set(permutations(iterable))
+ self.assertCountEqual(actual, expected)
+
+ def test_unsortable_r(self):
+ iterable = ['1', 2, 2, 3, 3, 3]
+ for r in range(len(iterable) + 1):
+ with self.subTest(iterable=iterable, r=r):
+ actual = list(mi.distinct_permutations(iterable, r=r))
+ expected = set(permutations(iterable, r=r))
+ self.assertCountEqual(actual, expected)
+
+ def test_unsorted_equivalent(self):
+ iterable = [1, True, '3']
+ actual = list(mi.distinct_permutations(iterable))
+ expected = set(permutations(iterable))
+ self.assertCountEqual(actual, expected)
+
+ def test_unhashable(self):
+ iterable = ([1], [1], 2)
+ actual = list(mi.distinct_permutations(iterable))
+ expected = list(mi.unique_everseen(permutations(iterable)))
+ self.assertCountEqual(actual, expected)
class IlenTests(TestCase):
@@ -2172,6 +2173,29 @@ class SortTogetherTest(TestCase):
],
)
+ def test_strict(self):
+ # Test for list of lists or tuples
+ self.assertRaises(
+ mi.UnequalIterablesError,
+ lambda: mi.sort_together(
+ [(4, 3, 2, 1), ('a', 'b', 'c')], strict=True
+ ),
+ )
+
+ # Test for list of iterables
+ self.assertRaises(
+ mi.UnequalIterablesError,
+ lambda: mi.sort_together([range(4), range(5)], strict=True),
+ )
+
+ # Test for iterable of iterables
+ self.assertRaises(
+ mi.UnequalIterablesError,
+ lambda: mi.sort_together(
+ (range(i) for i in range(4)), strict=True
+ ),
+ )
+
class DivideTest(TestCase):
"""Tests for divide()"""
@@ -3347,6 +3371,10 @@ class SeekableTest(PeekableMixinTests, TestCase):
self.assertEqual(next(s), '2')
s.relative_seek(-2)
self.assertEqual(next(s), '1')
+ s.relative_seek(-2)
+ self.assertEqual(
+ next(s), '0'
+ ) # Seek relative to current position within the cache
s.relative_seek(-10) # Lower bound
self.assertEqual(next(s), '0')
s.relative_seek(10) # Lower bound
@@ -3488,17 +3516,43 @@ class CircularShiftsTests(TestCase):
def test_simple_circular_shifts(self):
# test the a simple iterator case
self.assertEqual(
- mi.circular_shifts(range(4)),
+ list(mi.circular_shifts(range(4))),
[(0, 1, 2, 3), (1, 2, 3, 0), (2, 3, 0, 1), (3, 0, 1, 2)],
)
def test_duplicates(self):
# test non-distinct entries
self.assertEqual(
- mi.circular_shifts([0, 1, 0, 1]),
+ list(mi.circular_shifts([0, 1, 0, 1])),
[(0, 1, 0, 1), (1, 0, 1, 0), (0, 1, 0, 1), (1, 0, 1, 0)],
)
+ def test_steps_positive(self):
+ actual = list(mi.circular_shifts(range(5), steps=2))
+ expected = [
+ (0, 1, 2, 3, 4),
+ (2, 3, 4, 0, 1),
+ (4, 0, 1, 2, 3),
+ (1, 2, 3, 4, 0),
+ (3, 4, 0, 1, 2),
+ ]
+ self.assertEqual(actual, expected)
+
+ def test_steps_negative(self):
+ actual = list(mi.circular_shifts(range(5), steps=-2))
+ expected = [
+ (0, 1, 2, 3, 4),
+ (3, 4, 0, 1, 2),
+ (1, 2, 3, 4, 0),
+ (4, 0, 1, 2, 3),
+ (2, 3, 4, 0, 1),
+ ]
+ self.assertEqual(actual, expected)
+
+ def test_steps_zero(self):
+ with self.assertRaises(ValueError):
+ list(mi.circular_shifts(range(5), steps=0))
+
class MakeDecoratorTests(TestCase):
def test_basic(self):
@@ -3885,6 +3939,24 @@ class SetPartitionsTests(TestCase):
def test_to_many_groups(self):
self.assertEqual([], list(mi.set_partitions(range(4), 5)))
+ def test_min_size(self):
+ it = 'abc'
+ actual = mi.set_partitions(it, min_size=2)
+ expected = [['abc']]
+ self.assertEqual(
+ self._normalize_partitions(expected),
+ self._normalize_partitions(actual),
+ )
+
+ def test_max_size(self):
+ it = 'abc'
+ actual = mi.set_partitions(it, max_size=2)
+ expected = [['a', 'bc'], ['ab', 'c'], ['b', 'ac'], ['a', 'b', 'c']]
+ self.assertEqual(
+ self._normalize_partitions(expected),
+ self._normalize_partitions(actual),
+ )
+
class TimeLimitedTests(TestCase):
def test_basic(self):
@@ -4145,6 +4217,11 @@ class SampleTests(TestCase):
expected = ['f', 'e']
self.assertEqual(actual, expected)
+ def test_negative(self):
+ data = [1, 2, 3, 4, 5]
+ with self.assertRaises(ValueError):
+ mi.sample(data, k=-1)
+
def test_length(self):
"""Check that *k* elements are sampled."""
data = [1, 2, 3, 4, 5]
@@ -4154,6 +4231,32 @@ class SampleTests(TestCase):
expected = min(k, len(data))
self.assertEqual(actual, expected)
+ def test_strict(self):
+ data = ['1', '2', '3', '4', '5']
+ self.assertEqual(set(mi.sample(data, 6, strict=False)), set(data))
+ with self.assertRaises(ValueError):
+ mi.sample(data, 6, strict=True)
+
+ def test_counts(self):
+ # Test with counts
+ seed(0)
+ iterable = ['red', 'blue']
+ counts = [4, 2]
+ k = 5
+ actual = list(mi.sample(iterable, counts=counts, k=k))
+
+ # Test without counts
+ seed(0)
+ decoded_iterable = (['red'] * 4) + (['blue'] * 2)
+ expected = list(mi.sample(decoded_iterable, k=k))
+
+ self.assertEqual(actual, expected)
+
+ def test_counts_all(self):
+ actual = Counter(mi.sample('uwxyz', 35, counts=(1, 0, 4, 10, 20)))
+ expected = Counter({'u': 1, 'x': 4, 'y': 10, 'z': 20})
+ self.assertEqual(actual, expected)
+
def test_sampling_entire_iterable(self):
"""If k=len(iterable), the sample contains the original elements."""
data = ["a", 2, "a", 4, (1, 2, 3)]
@@ -4227,6 +4330,7 @@ class IsSortedTests(TestCase):
([1, 2, 3], {}, True),
([1, 1, 2, 3], {}, True),
([1, 10, 2, 3], {}, False),
+ ([3, float('nan'), 1, 2], {}, True),
(['1', '10', '2', '3'], {}, True),
(['1', '10', '2', '3'], {'key': int}, False),
([1, 2, 3], {'reverse': True}, False),
diff --git a/contrib/python/more-itertools/py3/tests/test_recipes.py b/contrib/python/more-itertools/py3/tests/test_recipes.py
index 0035e58d055..d3762d49dbe 100644
--- a/contrib/python/more-itertools/py3/tests/test_recipes.py
+++ b/contrib/python/more-itertools/py3/tests/test_recipes.py
@@ -838,7 +838,7 @@ class TriplewiseTests(TestCase):
class SlidingWindowTests(TestCase):
- def test_basic(self):
+ def test_islice_version(self):
for iterable, n, expected in [
([], 1, []),
([0], 1, [(0,)]),
@@ -853,6 +853,17 @@ class SlidingWindowTests(TestCase):
actual = list(mi.sliding_window(iterable, n))
self.assertEqual(actual, expected)
+ def test_deque_version(self):
+ iterable = map(str, range(100))
+ all_windows = list(mi.sliding_window(iterable, 95))
+ self.assertEqual(all_windows[0], tuple(map(str, range(95))))
+ self.assertEqual(all_windows[-1], tuple(map(str, range(5, 100))))
+
+ def test_zero(self):
+ iterable = map(str, range(100))
+ with self.assertRaises(ValueError):
+ list(mi.sliding_window(iterable, 0))
+
class SubslicesTests(TestCase):
def test_basic(self):
diff --git a/contrib/python/more-itertools/py3/ya.make b/contrib/python/more-itertools/py3/ya.make
index e902c9d552f..ee8f86bc142 100644
--- a/contrib/python/more-itertools/py3/ya.make
+++ b/contrib/python/more-itertools/py3/ya.make
@@ -2,7 +2,7 @@
PY3_LIBRARY()
-VERSION(10.3.0)
+VERSION(10.4.0)
LICENSE(MIT)
diff --git a/contrib/python/numpy/py2/numpy/core/src/multiarray/ya.make b/contrib/python/numpy/py2/numpy/core/src/multiarray/ya.make
index ea7aea99f3d..cd3d566a6a6 100644
--- a/contrib/python/numpy/py2/numpy/core/src/multiarray/ya.make
+++ b/contrib/python/numpy/py2/numpy/core/src/multiarray/ya.make
@@ -1,5 +1,7 @@
PY2_LIBRARY()
+VERSION(1.16.6)
+
LICENSE(BSD-3-Clause)
NO_COMPILER_WARNINGS()
diff --git a/contrib/python/numpy/py2/numpy/core/src/umath/ya.make b/contrib/python/numpy/py2/numpy/core/src/umath/ya.make
index 3d713128ddf..ef3b61a6b30 100644
--- a/contrib/python/numpy/py2/numpy/core/src/umath/ya.make
+++ b/contrib/python/numpy/py2/numpy/core/src/umath/ya.make
@@ -1,5 +1,7 @@
PY2_LIBRARY()
+VERSION(1.16.6)
+
LICENSE(BSD-3-Clause)
NO_COMPILER_WARNINGS()
diff --git a/contrib/python/numpy/py2/numpy/f2py/ya.make b/contrib/python/numpy/py2/numpy/f2py/ya.make
index 0501d5c3f8c..69f47f0e864 100644
--- a/contrib/python/numpy/py2/numpy/f2py/ya.make
+++ b/contrib/python/numpy/py2/numpy/f2py/ya.make
@@ -1,5 +1,7 @@
PY2_PROGRAM()
+VERSION(1.16.6)
+
LICENSE(BSD-3-Clause)
PEERDIR(
diff --git a/contrib/python/numpy/py2/numpy/fft/ya.make b/contrib/python/numpy/py2/numpy/fft/ya.make
index 5038991af20..069b07107e5 100644
--- a/contrib/python/numpy/py2/numpy/fft/ya.make
+++ b/contrib/python/numpy/py2/numpy/fft/ya.make
@@ -1,5 +1,7 @@
PY2_LIBRARY()
+VERSION(1.16.6)
+
LICENSE(BSD-3-Clause)
NO_COMPILER_WARNINGS()
diff --git a/contrib/python/numpy/py2/numpy/linalg/ya.make b/contrib/python/numpy/py2/numpy/linalg/ya.make
index e514b952fe1..cac0af2f974 100644
--- a/contrib/python/numpy/py2/numpy/linalg/ya.make
+++ b/contrib/python/numpy/py2/numpy/linalg/ya.make
@@ -1,5 +1,7 @@
PY2_LIBRARY()
+VERSION(1.16.6)
+
LICENSE(BSD-3-Clause)
NO_COMPILER_WARNINGS()
diff --git a/contrib/python/numpy/py2/numpy/random/mtrand/ya.make b/contrib/python/numpy/py2/numpy/random/mtrand/ya.make
index 1ea8f472a1a..64d7da65026 100644
--- a/contrib/python/numpy/py2/numpy/random/mtrand/ya.make
+++ b/contrib/python/numpy/py2/numpy/random/mtrand/ya.make
@@ -1,5 +1,7 @@
PY2_LIBRARY()
+VERSION(1.16.6)
+
LICENSE(BSD-3-Clause)
NO_COMPILER_WARNINGS()
diff --git a/contrib/python/numpy/py3/numpy/random/ya.make b/contrib/python/numpy/py3/numpy/random/ya.make
index 6cc93532fb7..9c428baeee3 100644
--- a/contrib/python/numpy/py3/numpy/random/ya.make
+++ b/contrib/python/numpy/py3/numpy/random/ya.make
@@ -1,5 +1,7 @@
PY3_LIBRARY()
+VERSION(1.26.4)
+
LICENSE(BSD-3-Clause)
ADDINCL(
diff --git a/contrib/python/ydb/py3/.dist-info/METADATA b/contrib/python/ydb/py3/.dist-info/METADATA
index 5b155408fcd..b1c2dcdaa21 100644
--- a/contrib/python/ydb/py3/.dist-info/METADATA
+++ b/contrib/python/ydb/py3/.dist-info/METADATA
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: ydb
-Version: 3.15.0
+Version: 3.16.0
Summary: YDB Python SDK
Home-page: http://github.com/ydb-platform/ydb-python-sdk
Author: Yandex LLC
diff --git a/contrib/python/ydb/py3/ya.make b/contrib/python/ydb/py3/ya.make
index cb2a88454aa..bc7c34dd4df 100644
--- a/contrib/python/ydb/py3/ya.make
+++ b/contrib/python/ydb/py3/ya.make
@@ -2,7 +2,7 @@
PY3_LIBRARY()
-VERSION(3.15.0)
+VERSION(3.16.0)
LICENSE(Apache-2.0)
@@ -57,6 +57,11 @@ PY_SRCS(
ydb/aio/iam.py
ydb/aio/oauth2_token_exchange.py
ydb/aio/pool.py
+ ydb/aio/query/__init__.py
+ ydb/aio/query/base.py
+ ydb/aio/query/pool.py
+ ydb/aio/query/session.py
+ ydb/aio/query/transaction.py
ydb/aio/resolver.py
ydb/aio/scheme.py
ydb/aio/table.py
diff --git a/contrib/python/ydb/py3/ydb/_grpc/grpcwrapper/common_utils.py b/contrib/python/ydb/py3/ydb/_grpc/grpcwrapper/common_utils.py
index 66ef0a8c855..95a5744313e 100644
--- a/contrib/python/ydb/py3/ydb/_grpc/grpcwrapper/common_utils.py
+++ b/contrib/python/ydb/py3/ydb/_grpc/grpcwrapper/common_utils.py
@@ -24,7 +24,8 @@ from google.protobuf.message import Message
from google.protobuf.duration_pb2 import Duration as ProtoDuration
from google.protobuf.timestamp_pb2 import Timestamp as ProtoTimeStamp
-import ydb.aio
+from ...driver import Driver
+from ...aio.driver import Driver as DriverIO
try:
from ydb.public.api.protos import ydb_topic_pb2, ydb_issue_message_pb2
@@ -141,7 +142,7 @@ class IGrpcWrapperAsyncIO(abc.ABC):
...
-SupportedDriverType = Union[ydb.Driver, ydb.aio.Driver]
+SupportedDriverType = Union[Driver, DriverIO]
class GrpcWrapperAsyncIO(IGrpcWrapperAsyncIO):
@@ -180,7 +181,7 @@ class GrpcWrapperAsyncIO(IGrpcWrapperAsyncIO):
if self._wait_executor:
self._wait_executor.shutdown(wait)
- async def _start_asyncio_driver(self, driver: ydb.aio.Driver, stub, method):
+ async def _start_asyncio_driver(self, driver: DriverIO, stub, method):
requests_iterator = QueueToIteratorAsyncIO(self.from_client_grpc)
stream_call = await driver(
requests_iterator,
@@ -190,7 +191,7 @@ class GrpcWrapperAsyncIO(IGrpcWrapperAsyncIO):
self._stream_call = stream_call
self.from_server_grpc = stream_call.__aiter__()
- async def _start_sync_driver(self, driver: ydb.Driver, stub, method):
+ async def _start_sync_driver(self, driver: Driver, stub, method):
requests_iterator = AsyncQueueToSyncIteratorAsyncIO(self.from_client_grpc)
self._wait_executor = concurrent.futures.ThreadPoolExecutor(max_workers=1)
diff --git a/contrib/python/ydb/py3/ydb/aio/__init__.py b/contrib/python/ydb/py3/ydb/aio/__init__.py
index acc44db57a2..0e7d4e747a9 100644
--- a/contrib/python/ydb/py3/ydb/aio/__init__.py
+++ b/contrib/python/ydb/py3/ydb/aio/__init__.py
@@ -1,2 +1,3 @@
from .driver import Driver # noqa
from .table import SessionPool, retry_operation # noqa
+from .query import QuerySessionPoolAsync, QuerySessionAsync # noqa
diff --git a/contrib/python/ydb/py3/ydb/aio/_utilities.py b/contrib/python/ydb/py3/ydb/aio/_utilities.py
index 10cbead6674..454378b0d40 100644
--- a/contrib/python/ydb/py3/ydb/aio/_utilities.py
+++ b/contrib/python/ydb/py3/ydb/aio/_utilities.py
@@ -7,6 +7,9 @@ class AsyncResponseIterator(object):
self.it.cancel()
return self
+ def __iter__(self):
+ return self
+
def __aiter__(self):
return self
diff --git a/contrib/python/ydb/py3/ydb/aio/query/__init__.py b/contrib/python/ydb/py3/ydb/aio/query/__init__.py
new file mode 100644
index 00000000000..829d7b54cf6
--- /dev/null
+++ b/contrib/python/ydb/py3/ydb/aio/query/__init__.py
@@ -0,0 +1,7 @@
+__all__ = [
+ "QuerySessionPoolAsync",
+ "QuerySessionAsync",
+]
+
+from .pool import QuerySessionPoolAsync
+from .session import QuerySessionAsync
diff --git a/contrib/python/ydb/py3/ydb/aio/query/base.py b/contrib/python/ydb/py3/ydb/aio/query/base.py
new file mode 100644
index 00000000000..3800ce3d4f0
--- /dev/null
+++ b/contrib/python/ydb/py3/ydb/aio/query/base.py
@@ -0,0 +1,11 @@
+from .. import _utilities
+
+
+class AsyncResponseContextIterator(_utilities.AsyncResponseIterator):
+ async def __aenter__(self) -> "AsyncResponseContextIterator":
+ return self
+
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
+ # To close stream on YDB it is necessary to scroll through it to the end
+ async for _ in self:
+ pass
diff --git a/contrib/python/ydb/py3/ydb/aio/query/pool.py b/contrib/python/ydb/py3/ydb/aio/query/pool.py
new file mode 100644
index 00000000000..f91f7465e41
--- /dev/null
+++ b/contrib/python/ydb/py3/ydb/aio/query/pool.py
@@ -0,0 +1,108 @@
+import logging
+from typing import (
+ Callable,
+ Optional,
+ List,
+)
+
+from .session import (
+ QuerySessionAsync,
+)
+from ...retries import (
+ RetrySettings,
+ retry_operation_async,
+)
+from ... import convert
+from ..._grpc.grpcwrapper import common_utils
+
+logger = logging.getLogger(__name__)
+
+
+class QuerySessionPoolAsync:
+ """QuerySessionPoolAsync is an object to simplify operations with sessions of Query Service."""
+
+ def __init__(self, driver: common_utils.SupportedDriverType):
+ """
+ :param driver: A driver instance
+ """
+
+ logger.warning("QuerySessionPoolAsync is an experimental API, which could be changed.")
+ self._driver = driver
+
+ def checkout(self) -> "SimpleQuerySessionCheckoutAsync":
+ """WARNING: This API is experimental and could be changed.
+ Return a Session context manager, that opens session on enter and closes session on exit.
+ """
+
+ return SimpleQuerySessionCheckoutAsync(self)
+
+ async def retry_operation_async(
+ self, callee: Callable, retry_settings: Optional[RetrySettings] = None, *args, **kwargs
+ ):
+ """WARNING: This API is experimental and could be changed.
+ Special interface to execute a bunch of commands with session in a safe, retriable way.
+
+ :param callee: A function, that works with session.
+ :param retry_settings: RetrySettings object.
+
+ :return: Result sets or exception in case of execution errors.
+ """
+
+ retry_settings = RetrySettings() if retry_settings is None else retry_settings
+
+ async def wrapped_callee():
+ async with self.checkout() as session:
+ return await callee(session, *args, **kwargs)
+
+ return await retry_operation_async(wrapped_callee, retry_settings)
+
+ async def execute_with_retries(
+ self,
+ query: str,
+ parameters: Optional[dict] = None,
+ retry_settings: Optional[RetrySettings] = None,
+ *args,
+ **kwargs,
+ ) -> List[convert.ResultSet]:
+ """WARNING: This API is experimental and could be changed.
+ Special interface to execute a one-shot queries in a safe, retriable way.
+ Note: this method loads all data from stream before return, do not use this
+ method with huge read queries.
+
+ :param query: A query, yql or sql text.
+ :param parameters: dict with parameters and YDB types;
+ :param retry_settings: RetrySettings object.
+
+ :return: Result sets or exception in case of execution errors.
+ """
+
+ retry_settings = RetrySettings() if retry_settings is None else retry_settings
+
+ async def wrapped_callee():
+ async with self.checkout() as session:
+ it = await session.execute(query, parameters, *args, **kwargs)
+ return [result_set async for result_set in it]
+
+ return await retry_operation_async(wrapped_callee, retry_settings)
+
+ async def stop(self, timeout=None):
+ pass # TODO: implement
+
+ async def __aenter__(self):
+ return self
+
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
+ await self.stop()
+
+
+class SimpleQuerySessionCheckoutAsync:
+ def __init__(self, pool: QuerySessionPoolAsync):
+ self._pool = pool
+ self._session = QuerySessionAsync(pool._driver)
+
+ async def __aenter__(self) -> QuerySessionAsync:
+ await self._session.create()
+ return self._session
+
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
+ await self._session.delete()
diff --git a/contrib/python/ydb/py3/ydb/aio/query/session.py b/contrib/python/ydb/py3/ydb/aio/query/session.py
new file mode 100644
index 00000000000..627a41d895d
--- /dev/null
+++ b/contrib/python/ydb/py3/ydb/aio/query/session.py
@@ -0,0 +1,144 @@
+import asyncio
+
+from typing import (
+ Optional,
+)
+
+from .base import AsyncResponseContextIterator
+from .transaction import QueryTxContextAsync
+from .. import _utilities
+from ... import issues
+from ..._grpc.grpcwrapper import common_utils
+from ..._grpc.grpcwrapper import ydb_query_public_types as _ydb_query_public
+
+from ...query import base
+from ...query.session import (
+ BaseQuerySession,
+ QuerySessionStateEnum,
+)
+
+
+class QuerySessionAsync(BaseQuerySession):
+ """Session object for Query Service. It is not recommended to control
+ session's lifecycle manually - use a QuerySessionPool is always a better choise.
+ """
+
+ _loop: asyncio.AbstractEventLoop
+ _status_stream: _utilities.AsyncResponseIterator = None
+
+ def __init__(
+ self,
+ driver: common_utils.SupportedDriverType,
+ settings: Optional[base.QueryClientSettings] = None,
+ loop: asyncio.AbstractEventLoop = None,
+ ):
+ super(QuerySessionAsync, self).__init__(driver, settings)
+ self._loop = loop if loop is not None else asyncio.get_running_loop()
+
+ async def _attach(self) -> None:
+ self._stream = await self._attach_call()
+ self._status_stream = _utilities.AsyncResponseIterator(
+ self._stream,
+ lambda response: common_utils.ServerStatus.from_proto(response),
+ )
+
+ first_response = await self._status_stream.next()
+ if first_response.status != issues.StatusCode.SUCCESS:
+ pass
+
+ self._state.set_attached(True)
+ self._state._change_state(QuerySessionStateEnum.CREATED)
+
+ self._loop.create_task(self._check_session_status_loop(), name="check session status task")
+
+ async def _check_session_status_loop(self) -> None:
+ try:
+ async for status in self._status_stream:
+ if status.status != issues.StatusCode.SUCCESS:
+ self._state.reset()
+ self._state._change_state(QuerySessionStateEnum.CLOSED)
+ except Exception:
+ if not self._state._already_in(QuerySessionStateEnum.CLOSED):
+ self._state.reset()
+ self._state._change_state(QuerySessionStateEnum.CLOSED)
+
+ async def delete(self) -> None:
+ """WARNING: This API is experimental and could be changed.
+
+ Deletes a Session of Query Service on server side and releases resources.
+
+ :return: None
+ """
+ if self._state._already_in(QuerySessionStateEnum.CLOSED):
+ return
+
+ self._state._check_invalid_transition(QuerySessionStateEnum.CLOSED)
+ await self._delete_call()
+ self._stream.cancel()
+
+ async def create(self) -> "QuerySessionAsync":
+ """WARNING: This API is experimental and could be changed.
+
+ Creates a Session of Query Service on server side and attaches it.
+
+ :return: QuerySessionSync object.
+ """
+ if self._state._already_in(QuerySessionStateEnum.CREATED):
+ return
+
+ self._state._check_invalid_transition(QuerySessionStateEnum.CREATED)
+ await self._create_call()
+ await self._attach()
+
+ return self
+
+ def transaction(self, tx_mode=None) -> QueryTxContextAsync:
+ self._state._check_session_ready_to_use()
+ tx_mode = tx_mode if tx_mode else _ydb_query_public.QuerySerializableReadWrite()
+
+ return QueryTxContextAsync(
+ self._driver,
+ self._state,
+ self,
+ tx_mode,
+ )
+
+ async def execute(
+ self,
+ query: str,
+ parameters: dict = None,
+ syntax: base.QuerySyntax = None,
+ exec_mode: base.QueryExecMode = None,
+ concurrent_result_sets: bool = False,
+ ) -> AsyncResponseContextIterator:
+ """WARNING: This API is experimental and could be changed.
+
+ Sends a query to Query Service
+ :param query: (YQL or SQL text) to be executed.
+ :param syntax: Syntax of the query, which is a one from the following choises:
+ 1) QuerySyntax.YQL_V1, which is default;
+ 2) QuerySyntax.PG.
+ :param parameters: dict with parameters and YDB types;
+ :param concurrent_result_sets: A flag to allow YDB mix parts of different result sets. Default is False;
+
+ :return: Iterator with result sets
+ """
+ self._state._check_session_ready_to_use()
+
+ stream_it = await self._execute_call(
+ query=query,
+ commit_tx=True,
+ syntax=syntax,
+ exec_mode=exec_mode,
+ parameters=parameters,
+ concurrent_result_sets=concurrent_result_sets,
+ )
+
+ return AsyncResponseContextIterator(
+ stream_it,
+ lambda resp: base.wrap_execute_query_response(
+ rpc_state=None,
+ response_pb=resp,
+ settings=self._settings,
+ ),
+ )
diff --git a/contrib/python/ydb/py3/ydb/aio/query/transaction.py b/contrib/python/ydb/py3/ydb/aio/query/transaction.py
new file mode 100644
index 00000000000..e9993fccc25
--- /dev/null
+++ b/contrib/python/ydb/py3/ydb/aio/query/transaction.py
@@ -0,0 +1,153 @@
+import logging
+from typing import (
+ Optional,
+)
+
+from .base import AsyncResponseContextIterator
+from ... import issues
+from ...query import base
+from ...query.transaction import (
+ BaseQueryTxContext,
+ QueryTxStateEnum,
+)
+
+logger = logging.getLogger(__name__)
+
+
+class QueryTxContextAsync(BaseQueryTxContext):
+ async def __aenter__(self) -> "QueryTxContextAsync":
+ """
+ Enters a context manager and returns a transaction
+
+ :return: A transaction instance
+ """
+ return self
+
+ async def __aexit__(self, *args, **kwargs):
+ """
+ Closes a transaction context manager and rollbacks transaction if
+ it is not finished explicitly
+ """
+ await self._ensure_prev_stream_finished()
+ if self._tx_state._state == QueryTxStateEnum.BEGINED:
+ # It's strictly recommended to close transactions directly
+ # by using commit_tx=True flag while executing statement or by
+ # .commit() or .rollback() methods, but here we trying to do best
+ # effort to avoid useless open transactions
+ logger.warning("Potentially leaked tx: %s", self._tx_state.tx_id)
+ try:
+ await self.rollback()
+ except issues.Error:
+ logger.warning("Failed to rollback leaked tx: %s", self._tx_state.tx_id)
+
+ async def _ensure_prev_stream_finished(self) -> None:
+ if self._prev_stream is not None:
+ async with self._prev_stream:
+ pass
+ self._prev_stream = None
+
+ async def begin(self, settings: Optional[base.QueryClientSettings] = None) -> "QueryTxContextAsync":
+ """WARNING: This API is experimental and could be changed.
+
+ Explicitly begins a transaction
+
+ :param settings: A request settings
+
+ :return: None or exception if begin is failed
+ """
+ await self._begin_call(settings)
+ return self
+
+ async def commit(self, settings: Optional[base.QueryClientSettings] = None) -> None:
+ """WARNING: This API is experimental and could be changed.
+
+ Calls commit on a transaction if it is open otherwise is no-op. If transaction execution
+ failed then this method raises PreconditionFailed.
+
+ :param settings: A request settings
+
+ :return: A committed transaction or exception if commit is failed
+ """
+ if self._tx_state._already_in(QueryTxStateEnum.COMMITTED):
+ return
+
+ if self._tx_state._state == QueryTxStateEnum.NOT_INITIALIZED:
+ self._tx_state._change_state(QueryTxStateEnum.COMMITTED)
+ return
+
+ await self._ensure_prev_stream_finished()
+
+ await self._commit_call(settings)
+
+ async def rollback(self, settings: Optional[base.QueryClientSettings] = None) -> None:
+ """WARNING: This API is experimental and could be changed.
+
+ Calls rollback on a transaction if it is open otherwise is no-op. If transaction execution
+ failed then this method raises PreconditionFailed.
+
+ :param settings: A request settings
+
+ :return: A committed transaction or exception if commit is failed
+ """
+ if self._tx_state._already_in(QueryTxStateEnum.ROLLBACKED):
+ return
+
+ if self._tx_state._state == QueryTxStateEnum.NOT_INITIALIZED:
+ self._tx_state._change_state(QueryTxStateEnum.ROLLBACKED)
+ return
+
+ await self._ensure_prev_stream_finished()
+
+ await self._rollback_call(settings)
+
+ async def execute(
+ self,
+ query: str,
+ parameters: Optional[dict] = None,
+ commit_tx: Optional[bool] = False,
+ syntax: Optional[base.QuerySyntax] = None,
+ exec_mode: Optional[base.QueryExecMode] = None,
+ concurrent_result_sets: Optional[bool] = False,
+ settings: Optional[base.QueryClientSettings] = None,
+ ) -> AsyncResponseContextIterator:
+ """WARNING: This API is experimental and could be changed.
+
+ Sends a query to Query Service
+ :param query: (YQL or SQL text) to be executed.
+ :param parameters: dict with parameters and YDB types;
+ :param commit_tx: A special flag that allows transaction commit.
+ :param syntax: Syntax of the query, which is a one from the following choises:
+ 1) QuerySyntax.YQL_V1, which is default;
+ 2) QuerySyntax.PG.
+ :param exec_mode: Exec mode of the query, which is a one from the following choises:
+ 1) QueryExecMode.EXECUTE, which is default;
+ 2) QueryExecMode.EXPLAIN;
+ 3) QueryExecMode.VALIDATE;
+ 4) QueryExecMode.PARSE.
+ :param concurrent_result_sets: A flag to allow YDB mix parts of different result sets. Default is False;
+
+ :return: Iterator with result sets
+ """
+ await self._ensure_prev_stream_finished()
+
+ stream_it = await self._execute_call(
+ query=query,
+ commit_tx=commit_tx,
+ syntax=syntax,
+ exec_mode=exec_mode,
+ parameters=parameters,
+ concurrent_result_sets=concurrent_result_sets,
+ )
+
+ settings = settings if settings is not None else self.session._settings
+ self._prev_stream = AsyncResponseContextIterator(
+ stream_it,
+ lambda resp: base.wrap_execute_query_response(
+ rpc_state=None,
+ response_pb=resp,
+ tx=self,
+ commit_tx=commit_tx,
+ settings=settings,
+ ),
+ )
+ return self._prev_stream
diff --git a/contrib/python/ydb/py3/ydb/query/__init__.py b/contrib/python/ydb/py3/ydb/query/__init__.py
index eb967abc206..40e512cd6bd 100644
--- a/contrib/python/ydb/py3/ydb/query/__init__.py
+++ b/contrib/python/ydb/py3/ydb/query/__init__.py
@@ -11,13 +11,12 @@ __all__ = [
import logging
from .base import (
- IQueryClient,
- SupportedDriverType,
QueryClientSettings,
)
from .session import QuerySessionSync
+from .._grpc.grpcwrapper import common_utils
from .._grpc.grpcwrapper.ydb_query_public_types import (
QueryOnlineReadOnly,
QuerySerializableReadWrite,
@@ -30,8 +29,8 @@ from .pool import QuerySessionPool
logger = logging.getLogger(__name__)
-class QueryClientSync(IQueryClient):
- def __init__(self, driver: SupportedDriverType, query_client_settings: QueryClientSettings = None):
+class QueryClientSync:
+ def __init__(self, driver: common_utils.SupportedDriverType, query_client_settings: QueryClientSettings = None):
logger.warning("QueryClientSync is an experimental API, which could be changed.")
self._driver = driver
self._settings = query_client_settings
diff --git a/contrib/python/ydb/py3/ydb/query/base.py b/contrib/python/ydb/py3/ydb/query/base.py
index e08d9f52d0e..55087d0c4ee 100644
--- a/contrib/python/ydb/py3/ydb/query/base.py
+++ b/contrib/python/ydb/py3/ydb/query/base.py
@@ -2,14 +2,11 @@ import abc
import enum
import functools
+import typing
from typing import (
- Iterator,
Optional,
)
-from .._grpc.grpcwrapper.common_utils import (
- SupportedDriverType,
-)
from .._grpc.grpcwrapper import ydb_query
from .._grpc.grpcwrapper.ydb_query_public_types import (
BaseQueryTxMode,
@@ -20,6 +17,9 @@ from .. import issues
from .. import _utilities
from .. import _apis
+if typing.TYPE_CHECKING:
+ from .transaction import BaseQueryTxContext
+
class QuerySyntax(enum.IntEnum):
UNSPECIFIED = 0
@@ -48,12 +48,38 @@ class SyncResponseContextIterator(_utilities.SyncResponseIterator):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
+ # To close stream on YDB it is necessary to scroll through it to the end
for _ in self:
pass
class QueryClientSettings:
- pass
+ def __init__(self):
+ self._native_datetime_in_result_sets = True
+ self._native_date_in_result_sets = True
+ self._native_json_in_result_sets = True
+ self._native_interval_in_result_sets = True
+ self._native_timestamp_in_result_sets = True
+
+ def with_native_timestamp_in_result_sets(self, enabled: bool) -> "QueryClientSettings":
+ self._native_timestamp_in_result_sets = enabled
+ return self
+
+ def with_native_interval_in_result_sets(self, enabled: bool) -> "QueryClientSettings":
+ self._native_interval_in_result_sets = enabled
+ return self
+
+ def with_native_json_in_result_sets(self, enabled: bool) -> "QueryClientSettings":
+ self._native_json_in_result_sets = enabled
+ return self
+
+ def with_native_date_in_result_sets(self, enabled: bool) -> "QueryClientSettings":
+ self._native_date_in_result_sets = enabled
+ return self
+
+ def with_native_datetime_in_result_sets(self, enabled: bool) -> "QueryClientSettings":
+ self._native_datetime_in_result_sets = enabled
+ return self
class IQuerySessionState(abc.ABC):
@@ -92,229 +118,6 @@ class IQuerySessionState(abc.ABC):
pass
-class IQuerySession(abc.ABC):
- """Session object for Query Service. It is not recommended to control
- session's lifecycle manually - use a QuerySessionPool is always a better choise.
- """
-
- @abc.abstractmethod
- def __init__(self, driver: SupportedDriverType, settings: Optional[QueryClientSettings] = None):
- pass
-
- @abc.abstractmethod
- def create(self) -> "IQuerySession":
- """WARNING: This API is experimental and could be changed.
-
- Creates a Session of Query Service on server side and attaches it.
-
- :return: Session object.
- """
- pass
-
- @abc.abstractmethod
- def delete(self) -> None:
- """WARNING: This API is experimental and could be changed.
-
- Deletes a Session of Query Service on server side and releases resources.
-
- :return: None
- """
- pass
-
- @abc.abstractmethod
- def transaction(self, tx_mode: Optional[BaseQueryTxMode] = None) -> "IQueryTxContext":
- """WARNING: This API is experimental and could be changed.
-
- Creates a transaction context manager with specified transaction mode.
-
- :param tx_mode: Transaction mode, which is a one from the following choises:
- 1) QuerySerializableReadWrite() which is default mode;
- 2) QueryOnlineReadOnly(allow_inconsistent_reads=False);
- 3) QuerySnapshotReadOnly();
- 4) QueryStaleReadOnly().
-
- :return: transaction context manager.
- """
- pass
-
- @abc.abstractmethod
- def execute(
- self,
- query: str,
- syntax: Optional[QuerySyntax] = None,
- exec_mode: Optional[QueryExecMode] = None,
- parameters: Optional[dict] = None,
- concurrent_result_sets: Optional[bool] = False,
- ) -> Iterator:
- """WARNING: This API is experimental and could be changed.
-
- Sends a query to Query Service
- :param query: (YQL or SQL text) to be executed.
- :param syntax: Syntax of the query, which is a one from the following choises:
- 1) QuerySyntax.YQL_V1, which is default;
- 2) QuerySyntax.PG.
- :param parameters: dict with parameters and YDB types;
- :param concurrent_result_sets: A flag to allow YDB mix parts of different result sets. Default is False;
-
- :return: Iterator with result sets
- """
-
-
-class IQueryTxContext(abc.ABC):
- """
- An object that provides a simple transaction context manager that allows statements execution
- in a transaction. You don't have to open transaction explicitly, because context manager encapsulates
- transaction control logic, and opens new transaction if:
- 1) By explicit .begin();
- 2) On execution of a first statement, which is strictly recommended method, because that avoids
- useless round trip
-
- This context manager is not thread-safe, so you should not manipulate on it concurrently.
- """
-
- @abc.abstractmethod
- def __init__(
- self,
- driver: SupportedDriverType,
- session_state: IQuerySessionState,
- session: IQuerySession,
- tx_mode: BaseQueryTxMode,
- ):
- """
- An object that provides a simple transaction context manager that allows statements execution
- in a transaction. You don't have to open transaction explicitly, because context manager encapsulates
- transaction control logic, and opens new transaction if:
-
- 1) By explicit .begin() method;
- 2) On execution of a first statement, which is strictly recommended method, because that avoids useless round trip
-
- This context manager is not thread-safe, so you should not manipulate on it concurrently.
-
- :param driver: A driver instance
- :param session_state: A state of session
- :param tx_mode: Transaction mode, which is a one from the following choises:
- 1) QuerySerializableReadWrite() which is default mode;
- 2) QueryOnlineReadOnly(allow_inconsistent_reads=False);
- 3) QuerySnapshotReadOnly();
- 4) QueryStaleReadOnly().
- """
- pass
-
- @abc.abstractmethod
- def __enter__(self) -> "IQueryTxContext":
- """
- Enters a context manager and returns a transaction
-
- :return: A transaction instance
- """
- pass
-
- @abc.abstractmethod
- def __exit__(self, *args, **kwargs):
- """
- Closes a transaction context manager and rollbacks transaction if
- it is not finished explicitly
- """
- pass
-
- @property
- @abc.abstractmethod
- def session_id(self) -> str:
- """
- A transaction's session id
-
- :return: A transaction's session id
- """
- pass
-
- @property
- @abc.abstractmethod
- def tx_id(self) -> Optional[str]:
- """
- Returns an id of open transaction or None otherwise
-
- :return: An id of open transaction or None otherwise
- """
- pass
-
- @abc.abstractmethod
- def begin(self, settings: Optional[QueryClientSettings] = None) -> None:
- """WARNING: This API is experimental and could be changed.
-
- Explicitly begins a transaction
-
- :param settings: A request settings
-
- :return: None or exception if begin is failed
- """
- pass
-
- @abc.abstractmethod
- def commit(self, settings: Optional[QueryClientSettings] = None) -> None:
- """WARNING: This API is experimental and could be changed.
-
- Calls commit on a transaction if it is open. If transaction execution
- failed then this method raises PreconditionFailed.
-
- :param settings: A request settings
-
- :return: None or exception if commit is failed
- """
- pass
-
- @abc.abstractmethod
- def rollback(self, settings: Optional[QueryClientSettings] = None) -> None:
- """WARNING: This API is experimental and could be changed.
-
- Calls rollback on a transaction if it is open. If transaction execution
- failed then this method raises PreconditionFailed.
-
- :param settings: A request settings
-
- :return: None or exception if rollback is failed
- """
- pass
-
- @abc.abstractmethod
- def execute(
- self,
- query: str,
- commit_tx: Optional[bool] = False,
- syntax: Optional[QuerySyntax] = None,
- exec_mode: Optional[QueryExecMode] = None,
- parameters: Optional[dict] = None,
- concurrent_result_sets: Optional[bool] = False,
- ) -> Iterator:
- """WARNING: This API is experimental and could be changed.
-
- Sends a query to Query Service
- :param query: (YQL or SQL text) to be executed.
- :param commit_tx: A special flag that allows transaction commit.
- :param syntax: Syntax of the query, which is a one from the following choises:
- 1) QuerySyntax.YQL_V1, which is default;
- 2) QuerySyntax.PG.
- :param exec_mode: Exec mode of the query, which is a one from the following choises:
- 1) QueryExecMode.EXECUTE, which is default;
- 2) QueryExecMode.EXPLAIN;
- 3) QueryExecMode.VALIDATE;
- 4) QueryExecMode.PARSE.
- :param parameters: dict with parameters and YDB types;
- :param concurrent_result_sets: A flag to allow YDB mix parts of different result sets. Default is False;
-
- :return: Iterator with result sets
- """
- pass
-
-
-class IQueryClient(abc.ABC):
- def __init__(self, driver: SupportedDriverType, query_client_settings: Optional[QueryClientSettings] = None):
- pass
-
- @abc.abstractmethod
- def session(self) -> IQuerySession:
- pass
-
-
def create_execute_query_request(
query: str,
session_id: str,
@@ -365,15 +168,16 @@ def create_execute_query_request(
def wrap_execute_query_response(
rpc_state: RpcState,
response_pb: _apis.ydb_query.ExecuteQueryResponsePart,
- tx: Optional[IQueryTxContext] = None,
+ tx: Optional["BaseQueryTxContext"] = None,
commit_tx: Optional[bool] = False,
+ settings: Optional[QueryClientSettings] = None,
) -> convert.ResultSet:
issues._process_response(response_pb)
if tx and response_pb.tx_meta and not tx.tx_id:
tx._move_to_beginned(response_pb.tx_meta.id)
if tx and commit_tx:
tx._move_to_commited()
- return convert.ResultSet.from_message(response_pb.result_set)
+ return convert.ResultSet.from_message(response_pb.result_set, settings)
def bad_session_handler(func):
diff --git a/contrib/python/ydb/py3/ydb/query/pool.py b/contrib/python/ydb/py3/ydb/query/pool.py
index e7514cdf76f..afe39f06236 100644
--- a/contrib/python/ydb/py3/ydb/query/pool.py
+++ b/contrib/python/ydb/py3/ydb/query/pool.py
@@ -5,7 +5,6 @@ from typing import (
List,
)
-from . import base
from .session import (
QuerySessionSync,
)
@@ -14,6 +13,8 @@ from ..retries import (
retry_operation_sync,
)
from .. import convert
+from .._grpc.grpcwrapper import common_utils
+
logger = logging.getLogger(__name__)
@@ -21,7 +22,7 @@ logger = logging.getLogger(__name__)
class QuerySessionPool:
"""QuerySessionPool is an object to simplify operations with sessions of Query Service."""
- def __init__(self, driver: base.SupportedDriverType):
+ def __init__(self, driver: common_utils.SupportedDriverType):
"""
:param driver: A driver instance
"""
@@ -55,7 +56,12 @@ class QuerySessionPool:
return retry_operation_sync(wrapped_callee, retry_settings)
def execute_with_retries(
- self, query: str, retry_settings: Optional[RetrySettings] = None, *args, **kwargs
+ self,
+ query: str,
+ parameters: Optional[dict] = None,
+ retry_settings: Optional[RetrySettings] = None,
+ *args,
+ **kwargs,
) -> List[convert.ResultSet]:
"""WARNING: This API is experimental and could be changed.
Special interface to execute a one-shot queries in a safe, retriable way.
@@ -63,6 +69,7 @@ class QuerySessionPool:
method with huge read queries.
:param query: A query, yql or sql text.
+ :param parameters: dict with parameters and YDB types;
:param retry_settings: RetrySettings object.
:return: Result sets or exception in case of execution errors.
@@ -72,18 +79,27 @@ class QuerySessionPool:
def wrapped_callee():
with self.checkout() as session:
- it = session.execute(query, *args, **kwargs)
+ it = session.execute(query, parameters, *args, **kwargs)
return [result_set for result_set in it]
return retry_operation_sync(wrapped_callee, retry_settings)
+ def stop(self, timeout=None):
+ pass # TODO: implement
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ self.stop()
+
class SimpleQuerySessionCheckout:
def __init__(self, pool: QuerySessionPool):
self._pool = pool
self._session = QuerySessionSync(pool._driver)
- def __enter__(self) -> base.IQuerySession:
+ def __enter__(self) -> QuerySessionSync:
self._session.create()
return self._session
diff --git a/contrib/python/ydb/py3/ydb/query/session.py b/contrib/python/ydb/py3/ydb/query/session.py
index d6034d348a5..4b051dc16f7 100644
--- a/contrib/python/ydb/py3/ydb/query/session.py
+++ b/contrib/python/ydb/py3/ydb/query/session.py
@@ -15,7 +15,7 @@ from .._grpc.grpcwrapper import common_utils
from .._grpc.grpcwrapper import ydb_query as _ydb_query
from .._grpc.grpcwrapper import ydb_query_public_types as _ydb_query_public
-from .transaction import BaseQueryTxContext
+from .transaction import QueryTxContextSync
logger = logging.getLogger(__name__)
@@ -126,12 +126,12 @@ def wrapper_delete_session(
return session
-class BaseQuerySession(base.IQuerySession):
- _driver: base.SupportedDriverType
+class BaseQuerySession:
+ _driver: common_utils.SupportedDriverType
_settings: base.QueryClientSettings
_state: QuerySessionState
- def __init__(self, driver: base.SupportedDriverType, settings: Optional[base.QueryClientSettings] = None):
+ def __init__(self, driver: common_utils.SupportedDriverType, settings: Optional[base.QueryClientSettings] = None):
self._driver = driver
self._settings = settings if settings is not None else base.QueryClientSettings()
self._state = QuerySessionState(settings)
@@ -224,7 +224,9 @@ class QuerySessionSync(BaseQuerySession):
self._state.reset()
self._state._change_state(QuerySessionStateEnum.CLOSED)
except Exception:
- pass
+ if not self._state._already_in(QuerySessionStateEnum.CLOSED):
+ self._state.reset()
+ self._state._change_state(QuerySessionStateEnum.CLOSED)
def delete(self) -> None:
"""WARNING: This API is experimental and could be changed.
@@ -256,7 +258,7 @@ class QuerySessionSync(BaseQuerySession):
return self
- def transaction(self, tx_mode: Optional[base.BaseQueryTxMode] = None) -> base.IQueryTxContext:
+ def transaction(self, tx_mode: Optional[base.BaseQueryTxMode] = None) -> QueryTxContextSync:
"""WARNING: This API is experimental and could be changed.
Creates a transaction context manager with specified transaction mode.
@@ -273,7 +275,7 @@ class QuerySessionSync(BaseQuerySession):
tx_mode = tx_mode if tx_mode else _ydb_query_public.QuerySerializableReadWrite()
- return BaseQueryTxContext(
+ return QueryTxContextSync(
self._driver,
self._state,
self,
@@ -283,9 +285,9 @@ class QuerySessionSync(BaseQuerySession):
def execute(
self,
query: str,
+ parameters: dict = None,
syntax: base.QuerySyntax = None,
exec_mode: base.QueryExecMode = None,
- parameters: dict = None,
concurrent_result_sets: bool = False,
) -> base.SyncResponseContextIterator:
"""WARNING: This API is experimental and could be changed.
@@ -313,5 +315,9 @@ class QuerySessionSync(BaseQuerySession):
return base.SyncResponseContextIterator(
stream_it,
- lambda resp: base.wrap_execute_query_response(rpc_state=None, response_pb=resp),
+ lambda resp: base.wrap_execute_query_response(
+ rpc_state=None,
+ response_pb=resp,
+ settings=self._settings,
+ ),
)
diff --git a/contrib/python/ydb/py3/ydb/query/transaction.py b/contrib/python/ydb/py3/ydb/query/transaction.py
index 0a49320293b..750a94b0b57 100644
--- a/contrib/python/ydb/py3/ydb/query/transaction.py
+++ b/contrib/python/ydb/py3/ydb/query/transaction.py
@@ -169,7 +169,7 @@ def wrap_tx_rollback_response(
return tx
-class BaseQueryTxContext(base.IQueryTxContext):
+class BaseQueryTxContext:
def __init__(self, driver, session_state, session, tx_mode):
"""
An object that provides a simple transaction context manager that allows statements execution
@@ -196,31 +196,6 @@ class BaseQueryTxContext(base.IQueryTxContext):
self.session = session
self._prev_stream = None
- def __enter__(self) -> "BaseQueryTxContext":
- """
- Enters a context manager and returns a transaction
-
- :return: A transaction instance
- """
- return self
-
- def __exit__(self, *args, **kwargs):
- """
- Closes a transaction context manager and rollbacks transaction if
- it is not finished explicitly
- """
- self._ensure_prev_stream_finished()
- if self._tx_state._state == QueryTxStateEnum.BEGINED:
- # It's strictly recommended to close transactions directly
- # by using commit_tx=True flag while executing statement or by
- # .commit() or .rollback() methods, but here we trying to do best
- # effort to avoid useless open transactions
- logger.warning("Potentially leaked tx: %s", self._tx_state.tx_id)
- try:
- self.rollback()
- except issues.Error:
- logger.warning("Failed to rollback leaked tx: %s", self._tx_state.tx_id)
-
@property
def session_id(self) -> str:
"""
@@ -240,6 +215,8 @@ class BaseQueryTxContext(base.IQueryTxContext):
return self._tx_state.tx_id
def _begin_call(self, settings: Optional[base.QueryClientSettings]) -> "BaseQueryTxContext":
+ self._tx_state._check_invalid_transition(QueryTxStateEnum.BEGINED)
+
return self._driver(
_create_begin_transaction_request(self._session_state, self._tx_state),
_apis.QueryService.Stub,
@@ -250,6 +227,8 @@ class BaseQueryTxContext(base.IQueryTxContext):
)
def _commit_call(self, settings: Optional[base.QueryClientSettings]) -> "BaseQueryTxContext":
+ self._tx_state._check_invalid_transition(QueryTxStateEnum.COMMITTED)
+
return self._driver(
_create_commit_transaction_request(self._session_state, self._tx_state),
_apis.QueryService.Stub,
@@ -260,6 +239,8 @@ class BaseQueryTxContext(base.IQueryTxContext):
)
def _rollback_call(self, settings: Optional[base.QueryClientSettings]) -> "BaseQueryTxContext":
+ self._tx_state._check_invalid_transition(QueryTxStateEnum.ROLLBACKED)
+
return self._driver(
_create_rollback_transaction_request(self._session_state, self._tx_state),
_apis.QueryService.Stub,
@@ -278,6 +259,8 @@ class BaseQueryTxContext(base.IQueryTxContext):
parameters: dict = None,
concurrent_result_sets: bool = False,
) -> Iterable[_apis.ydb_query.ExecuteQueryResponsePart]:
+ self._tx_state._check_tx_ready_to_use()
+
request = base.create_execute_query_request(
query=query,
session_id=self._session_state.session_id,
@@ -296,12 +279,6 @@ class BaseQueryTxContext(base.IQueryTxContext):
_apis.QueryService.ExecuteQuery,
)
- def _ensure_prev_stream_finished(self) -> None:
- if self._prev_stream is not None:
- for _ in self._prev_stream:
- pass
- self._prev_stream = None
-
def _move_to_beginned(self, tx_id: str) -> None:
if self._tx_state._already_in(QueryTxStateEnum.BEGINED):
return
@@ -313,19 +290,52 @@ class BaseQueryTxContext(base.IQueryTxContext):
return
self._tx_state._change_state(QueryTxStateEnum.COMMITTED)
- def begin(self, settings: Optional[base.QueryClientSettings] = None) -> None:
+
+class QueryTxContextSync(BaseQueryTxContext):
+ def __enter__(self) -> "BaseQueryTxContext":
+ """
+ Enters a context manager and returns a transaction
+
+ :return: A transaction instance
+ """
+ return self
+
+ def __exit__(self, *args, **kwargs):
+ """
+ Closes a transaction context manager and rollbacks transaction if
+ it is not finished explicitly
+ """
+ self._ensure_prev_stream_finished()
+ if self._tx_state._state == QueryTxStateEnum.BEGINED:
+ # It's strictly recommended to close transactions directly
+ # by using commit_tx=True flag while executing statement or by
+ # .commit() or .rollback() methods, but here we trying to do best
+ # effort to avoid useless open transactions
+ logger.warning("Potentially leaked tx: %s", self._tx_state.tx_id)
+ try:
+ self.rollback()
+ except issues.Error:
+ logger.warning("Failed to rollback leaked tx: %s", self._tx_state.tx_id)
+
+ def _ensure_prev_stream_finished(self) -> None:
+ if self._prev_stream is not None:
+ with self._prev_stream:
+ pass
+ self._prev_stream = None
+
+ def begin(self, settings: Optional[base.QueryClientSettings] = None) -> "QueryTxContextSync":
"""WARNING: This API is experimental and could be changed.
Explicitly begins a transaction
:param settings: A request settings
- :return: None or exception if begin is failed
+ :return: Transaction object or exception if begin is failed
"""
- self._tx_state._check_invalid_transition(QueryTxStateEnum.BEGINED)
-
self._begin_call(settings)
+ return self
+
def commit(self, settings: Optional[base.QueryClientSettings] = None) -> None:
"""WARNING: This API is experimental and could be changed.
@@ -338,43 +348,51 @@ class BaseQueryTxContext(base.IQueryTxContext):
"""
if self._tx_state._already_in(QueryTxStateEnum.COMMITTED):
return
- self._ensure_prev_stream_finished()
if self._tx_state._state == QueryTxStateEnum.NOT_INITIALIZED:
self._tx_state._change_state(QueryTxStateEnum.COMMITTED)
return
- self._tx_state._check_invalid_transition(QueryTxStateEnum.COMMITTED)
+ self._ensure_prev_stream_finished()
self._commit_call(settings)
def rollback(self, settings: Optional[base.QueryClientSettings] = None) -> None:
+ """WARNING: This API is experimental and could be changed.
+
+ Calls rollback on a transaction if it is open otherwise is no-op. If transaction execution
+ failed then this method raises PreconditionFailed.
+
+ :param settings: A request settings
+
+ :return: A committed transaction or exception if commit is failed
+ """
if self._tx_state._already_in(QueryTxStateEnum.ROLLBACKED):
return
- self._ensure_prev_stream_finished()
-
if self._tx_state._state == QueryTxStateEnum.NOT_INITIALIZED:
self._tx_state._change_state(QueryTxStateEnum.ROLLBACKED)
return
- self._tx_state._check_invalid_transition(QueryTxStateEnum.ROLLBACKED)
+ self._ensure_prev_stream_finished()
self._rollback_call(settings)
def execute(
self,
query: str,
+ parameters: Optional[dict] = None,
commit_tx: Optional[bool] = False,
syntax: Optional[base.QuerySyntax] = None,
exec_mode: Optional[base.QueryExecMode] = None,
- parameters: Optional[dict] = None,
concurrent_result_sets: Optional[bool] = False,
+ settings: Optional[base.QueryClientSettings] = None,
) -> base.SyncResponseContextIterator:
"""WARNING: This API is experimental and could be changed.
Sends a query to Query Service
:param query: (YQL or SQL text) to be executed.
+ :param parameters: dict with parameters and YDB types;
:param commit_tx: A special flag that allows transaction commit.
:param syntax: Syntax of the query, which is a one from the following choises:
1) QuerySyntax.YQL_V1, which is default;
@@ -384,13 +402,12 @@ class BaseQueryTxContext(base.IQueryTxContext):
2) QueryExecMode.EXPLAIN;
3) QueryExecMode.VALIDATE;
4) QueryExecMode.PARSE.
- :param parameters: dict with parameters and YDB types;
:param concurrent_result_sets: A flag to allow YDB mix parts of different result sets. Default is False;
+ :param settings: An additional request settings QueryClientSettings;
:return: Iterator with result sets
"""
self._ensure_prev_stream_finished()
- self._tx_state._check_tx_ready_to_use()
stream_it = self._execute_call(
query=query,
@@ -400,6 +417,8 @@ class BaseQueryTxContext(base.IQueryTxContext):
parameters=parameters,
concurrent_result_sets=concurrent_result_sets,
)
+
+ settings = settings if settings is not None else self.session._settings
self._prev_stream = base.SyncResponseContextIterator(
stream_it,
lambda resp: base.wrap_execute_query_response(
@@ -407,6 +426,7 @@ class BaseQueryTxContext(base.IQueryTxContext):
response_pb=resp,
tx=self,
commit_tx=commit_tx,
+ settings=settings,
),
)
return self._prev_stream
diff --git a/contrib/python/ydb/py3/ydb/retries.py b/contrib/python/ydb/py3/ydb/retries.py
index 5d4f6e6a0f7..c9c23b1a918 100644
--- a/contrib/python/ydb/py3/ydb/retries.py
+++ b/contrib/python/ydb/py3/ydb/retries.py
@@ -1,3 +1,4 @@
+import asyncio
import random
import time
@@ -134,3 +135,27 @@ def retry_operation_sync(callee, retry_settings=None, *args, **kwargs):
time.sleep(next_opt.timeout)
else:
return next_opt.result
+
+
+async def retry_operation_async(callee, retry_settings=None, *args, **kwargs): # pylint: disable=W1113
+ """
+ The retry operation helper can be used to retry a coroutine that raises YDB specific
+ exceptions.
+
+ :param callee: A coroutine to retry.
+ :param retry_settings: An instance of ydb.RetrySettings that describes how the coroutine
+ should be retried. If None, default instance of retry settings will be used.
+ :param args: A tuple with positional arguments to be passed into the coroutine.
+ :param kwargs: A dictionary with keyword arguments to be passed into the coroutine.
+
+ Returns awaitable result of coroutine. If retries are not succussful exception is raised.
+ """
+ opt_generator = retry_operation_impl(callee, retry_settings, *args, **kwargs)
+ for next_opt in opt_generator:
+ if isinstance(next_opt, YdbRetryOperationSleepOpt):
+ await asyncio.sleep(next_opt.timeout)
+ else:
+ try:
+ return await next_opt.result
+ except BaseException as e: # pylint: disable=W0703
+ next_opt.set_exception(e)
diff --git a/contrib/python/ydb/py3/ydb/ydb_version.py b/contrib/python/ydb/py3/ydb/ydb_version.py
index 567cda12c76..76caab7b483 100644
--- a/contrib/python/ydb/py3/ydb/ydb_version.py
+++ b/contrib/python/ydb/py3/ydb/ydb_version.py
@@ -1 +1 @@
-VERSION = "3.15.0"
+VERSION = "3.16.0"