summaryrefslogtreecommitdiffstats
path: root/contrib/python
diff options
context:
space:
mode:
authorAlexSm <[email protected]>2024-04-08 16:46:46 +0200
committerGitHub <[email protected]>2024-04-08 16:46:46 +0200
commit5ebf1bc5956c43c050a7b08d9c266f35a10e2e71 (patch)
tree39b3697735fab3b6872fa0c94875afc4c1938c29 /contrib/python
parentba3d69df82d1e10f6bbac0f98da0217f2287692f (diff)
parent2ccadbed5e49091384169de142a3829bd7c18f4f (diff)
Merge pull request #3543 from ydb-platform/mergelibs-240408-0819
Library import 240408-0819
Diffstat (limited to 'contrib/python')
-rw-r--r--contrib/python/PyJWT/py2/.dist-info/METADATA115
-rw-r--r--contrib/python/PyJWT/py2/.dist-info/entry_points.txt3
-rw-r--r--contrib/python/PyJWT/py2/.dist-info/top_level.txt1
-rw-r--r--contrib/python/PyJWT/py2/jwt/__init__.py31
-rw-r--r--contrib/python/PyJWT/py2/jwt/__main__.py168
-rw-r--r--contrib/python/PyJWT/py2/jwt/algorithms.py403
-rw-r--r--contrib/python/PyJWT/py2/jwt/api_jws.py242
-rw-r--r--contrib/python/PyJWT/py2/jwt/api_jwt.py222
-rw-r--r--contrib/python/PyJWT/py2/jwt/compat.py68
-rw-r--r--contrib/python/PyJWT/py2/jwt/contrib/__init__.py (renamed from contrib/python/tzlocal/py3/tzlocal/py.typed)0
-rw-r--r--contrib/python/PyJWT/py2/jwt/contrib/algorithms/__init__.py0
-rw-r--r--contrib/python/PyJWT/py2/jwt/contrib/algorithms/py_ecdsa.py60
-rw-r--r--contrib/python/PyJWT/py2/jwt/contrib/algorithms/pycrypto.py46
-rw-r--r--contrib/python/PyJWT/py2/jwt/exceptions.py59
-rw-r--r--contrib/python/PyJWT/py2/jwt/help.py61
-rw-r--r--contrib/python/PyJWT/py2/jwt/utils.py113
-rw-r--r--contrib/python/PyJWT/py2/ya.make43
-rw-r--r--contrib/python/cffi/py2/.dist-info/METADATA5
-rw-r--r--contrib/python/cffi/py2/.dist-info/entry_points.txt1
-rw-r--r--contrib/python/future/py2/future/backports/http/cookies.py2
-rw-r--r--contrib/python/future/py2/future/backports/xmlrpc/client.py9
-rw-r--r--contrib/python/future/py2/future/moves/_dummy_thread.py5
-rw-r--r--contrib/python/future/py2/future/standard_library/__init__.py2
-rw-r--r--contrib/python/google-auth/py3/.dist-info/METADATA2
-rw-r--r--contrib/python/google-auth/py3/google/auth/aws.py618
-rw-r--r--contrib/python/google-auth/py3/google/auth/external_account.py51
-rw-r--r--contrib/python/google-auth/py3/google/auth/identity_pool.py275
-rw-r--r--contrib/python/google-auth/py3/google/auth/version.py2
-rw-r--r--contrib/python/google-auth/py3/tests/test_aws.py498
-rw-r--r--contrib/python/google-auth/py3/tests/test_external_account.py32
-rw-r--r--contrib/python/google-auth/py3/tests/test_identity_pool.py165
-rw-r--r--contrib/python/google-auth/py3/ya.make2
-rw-r--r--contrib/python/hypothesis/py3/.dist-info/METADATA2
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/internal/conjecture/data.py48
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/internal/conjecture/shrinker.py4
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/internal/reflection.py7
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/version.py2
-rw-r--r--contrib/python/hypothesis/py3/ya.make2
-rw-r--r--contrib/python/importlib-metadata/py3/.dist-info/METADATA3
-rw-r--r--contrib/python/importlib-metadata/py3/importlib_metadata/__init__.py18
-rw-r--r--contrib/python/importlib-metadata/py3/importlib_metadata/compat/__init__.py0
-rw-r--r--contrib/python/importlib-metadata/py3/importlib_metadata/compat/py39.py (renamed from contrib/python/importlib-metadata/py3/importlib_metadata/_py39compat.py)6
-rw-r--r--contrib/python/importlib-metadata/py3/ya.make5
-rw-r--r--contrib/python/lz4/py2/.dist-info/METADATA3
-rw-r--r--contrib/python/numpy/py3/numpy/__init__.py2
-rw-r--r--contrib/python/pytest-mock/py3/.dist-info/METADATA16
-rw-r--r--contrib/python/pytest-mock/py3/pytest_mock/__init__.py8
-rw-r--r--contrib/python/pytest-mock/py3/pytest_mock/_version.py4
-rw-r--r--contrib/python/pytest-mock/py3/pytest_mock/plugin.py165
-rw-r--r--contrib/python/pytest-mock/py3/ya.make2
-rw-r--r--contrib/python/requests-oauthlib/.dist-info/METADATA13
-rw-r--r--contrib/python/requests-oauthlib/requests_oauthlib/__init__.py2
-rw-r--r--contrib/python/requests-oauthlib/tests/examples/__init__.py0
-rw-r--r--contrib/python/requests-oauthlib/ya.make2
-rw-r--r--contrib/python/ruamel.yaml.clib/py2/.dist-info/METADATA1
-rw-r--r--contrib/python/simplejson/py2/.dist-info/METADATA3
-rw-r--r--contrib/python/tzlocal/py2/.dist-info/METADATA326
-rw-r--r--contrib/python/tzlocal/py2/.dist-info/top_level.txt1
-rw-r--r--contrib/python/tzlocal/py2/LICENSE.txt19
-rw-r--r--contrib/python/tzlocal/py2/README.rst105
-rw-r--r--contrib/python/tzlocal/py2/tzlocal/__init__.py5
-rw-r--r--contrib/python/tzlocal/py2/tzlocal/unix.py174
-rw-r--r--contrib/python/tzlocal/py2/tzlocal/utils.py46
-rw-r--r--contrib/python/tzlocal/py2/tzlocal/win32.py104
-rw-r--r--contrib/python/tzlocal/py2/tzlocal/windows_tz.py697
-rw-r--r--contrib/python/tzlocal/py2/ya.make34
-rw-r--r--contrib/python/tzlocal/py3/.dist-info/METADATA248
-rw-r--r--contrib/python/tzlocal/py3/.dist-info/top_level.txt1
-rw-r--r--contrib/python/tzlocal/py3/LICENSE.txt19
-rw-r--r--contrib/python/tzlocal/py3/README.rst215
-rw-r--r--contrib/python/tzlocal/py3/tzlocal/__init__.py19
-rw-r--r--contrib/python/tzlocal/py3/tzlocal/unix.py231
-rw-r--r--contrib/python/tzlocal/py3/tzlocal/utils.py112
-rw-r--r--contrib/python/tzlocal/py3/tzlocal/win32.py147
-rw-r--r--contrib/python/tzlocal/py3/tzlocal/windows_tz.py736
-rw-r--r--contrib/python/tzlocal/py3/ya.make35
-rw-r--r--contrib/python/tzlocal/ya.make18
-rw-r--r--contrib/python/ydb/py3/.dist-info/METADATA3
-rw-r--r--contrib/python/ydb/py3/ya.make3
-rw-r--r--contrib/python/ydb/py3/ydb/aio/iam.py109
-rw-r--r--contrib/python/ydb/py3/ydb/driver.py7
-rw-r--r--contrib/python/ydb/py3/ydb/iam/__init__.py1
-rw-r--r--contrib/python/ydb/py3/ydb/iam/auth.py151
-rw-r--r--contrib/python/ydb/py3/ydb/ydb_version.py2
-rw-r--r--contrib/python/ydb/ya.make23
85 files changed, 3259 insertions, 3954 deletions
diff --git a/contrib/python/PyJWT/py2/.dist-info/METADATA b/contrib/python/PyJWT/py2/.dist-info/METADATA
new file mode 100644
index 00000000000..47ee5589077
--- /dev/null
+++ b/contrib/python/PyJWT/py2/.dist-info/METADATA
@@ -0,0 +1,115 @@
+Metadata-Version: 2.1
+Name: PyJWT
+Version: 1.7.1
+Summary: JSON Web Token implementation in Python
+Home-page: http://github.com/jpadilla/pyjwt
+Author: Jose Padilla
+Author-email: [email protected]
+License: MIT
+Keywords: jwt json web token security signing
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: Natural Language :: English
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Topic :: Utilities
+Provides-Extra: crypto
+Requires-Dist: cryptography (>=1.4) ; extra == 'crypto'
+Provides-Extra: flake8
+Requires-Dist: flake8 ; extra == 'flake8'
+Requires-Dist: flake8-import-order ; extra == 'flake8'
+Requires-Dist: pep8-naming ; extra == 'flake8'
+Provides-Extra: test
+Requires-Dist: pytest (<5.0.0,>=4.0.1) ; extra == 'test'
+Requires-Dist: pytest-cov (<3.0.0,>=2.6.0) ; extra == 'test'
+Requires-Dist: pytest-runner (<5.0.0,>=4.2) ; extra == 'test'
+
+PyJWT
+=====
+
+.. image:: https://travis-ci.com/jpadilla/pyjwt.svg?branch=master
+ :target: http://travis-ci.com/jpadilla/pyjwt?branch=master
+
+.. image:: https://ci.appveyor.com/api/projects/status/h8nt70aqtwhht39t?svg=true
+ :target: https://ci.appveyor.com/project/jpadilla/pyjwt
+
+.. image:: https://img.shields.io/pypi/v/pyjwt.svg
+ :target: https://pypi.python.org/pypi/pyjwt
+
+.. image:: https://coveralls.io/repos/jpadilla/pyjwt/badge.svg?branch=master
+ :target: https://coveralls.io/r/jpadilla/pyjwt?branch=master
+
+.. image:: https://readthedocs.org/projects/pyjwt/badge/?version=latest
+ :target: https://pyjwt.readthedocs.io
+
+A Python implementation of `RFC 7519 <https://tools.ietf.org/html/rfc7519>`_. Original implementation was written by `@progrium <https://github.com/progrium>`_.
+
+Sponsor
+-------
+
++--------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
+| |auth0-logo| | If you want to quickly add secure token-based authentication to Python projects, feel free to check Auth0's Python SDK and free plan at `auth0.com/overview <https://auth0.com/overview?utm_source=GHsponsor&utm_medium=GHsponsor&utm_campaign=pyjwt&utm_content=auth>`_. |
++--------------+-----------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
+
+.. |auth0-logo| image:: https://user-images.githubusercontent.com/83319/31722733-de95bbde-b3ea-11e7-96bf-4f4e8f915588.png
+
+Installing
+----------
+
+Install with **pip**:
+
+.. code-block:: sh
+
+ $ pip install PyJWT
+
+
+Usage
+-----
+
+.. code:: python
+
+ >>> import jwt
+ >>> encoded = jwt.encode({'some': 'payload'}, 'secret', algorithm='HS256')
+ 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.4twFt5NiznN84AWoo1d7KO1T_yoc0Z6XOpOVswacPZg'
+
+ >>> jwt.decode(encoded, 'secret', algorithms=['HS256'])
+ {'some': 'payload'}
+
+
+Command line
+------------
+
+Usage::
+
+ pyjwt [options] INPUT
+
+Decoding examples::
+
+ pyjwt --key=secret decode TOKEN
+ pyjwt decode --no-verify TOKEN
+
+See more options executing ``pyjwt --help``.
+
+
+Documentation
+-------------
+
+View the full docs online at https://pyjwt.readthedocs.io/en/latest/
+
+
+Tests
+-----
+
+You can run tests from the project root after cloning with:
+
+.. code-block:: sh
+
+ $ python setup.py test
+
+
diff --git a/contrib/python/PyJWT/py2/.dist-info/entry_points.txt b/contrib/python/PyJWT/py2/.dist-info/entry_points.txt
new file mode 100644
index 00000000000..78717b26610
--- /dev/null
+++ b/contrib/python/PyJWT/py2/.dist-info/entry_points.txt
@@ -0,0 +1,3 @@
+[console_scripts]
+pyjwt = jwt.__main__:main
+
diff --git a/contrib/python/PyJWT/py2/.dist-info/top_level.txt b/contrib/python/PyJWT/py2/.dist-info/top_level.txt
new file mode 100644
index 00000000000..27ccc9bc3a9
--- /dev/null
+++ b/contrib/python/PyJWT/py2/.dist-info/top_level.txt
@@ -0,0 +1 @@
+jwt
diff --git a/contrib/python/PyJWT/py2/jwt/__init__.py b/contrib/python/PyJWT/py2/jwt/__init__.py
new file mode 100644
index 00000000000..946983f0221
--- /dev/null
+++ b/contrib/python/PyJWT/py2/jwt/__init__.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+# flake8: noqa
+
+"""
+JSON Web Token implementation
+
+Minimum implementation based on this spec:
+http://self-issued.info/docs/draft-jones-json-web-token-01.html
+"""
+
+
+__title__ = 'pyjwt'
+__version__ = '1.7.1'
+__author__ = 'José Padilla'
+__license__ = 'MIT'
+__copyright__ = 'Copyright 2015-2018 José Padilla'
+
+
+from .api_jwt import (
+ encode, decode, register_algorithm, unregister_algorithm,
+ get_unverified_header, PyJWT
+)
+from .api_jws import PyJWS
+from .exceptions import (
+ InvalidTokenError, DecodeError, InvalidAlgorithmError,
+ InvalidAudienceError, ExpiredSignatureError, ImmatureSignatureError,
+ InvalidIssuedAtError, InvalidIssuerError, ExpiredSignature,
+ InvalidAudience, InvalidIssuer, MissingRequiredClaimError,
+ InvalidSignatureError,
+ PyJWTError,
+)
diff --git a/contrib/python/PyJWT/py2/jwt/__main__.py b/contrib/python/PyJWT/py2/jwt/__main__.py
new file mode 100644
index 00000000000..bf50aabf4af
--- /dev/null
+++ b/contrib/python/PyJWT/py2/jwt/__main__.py
@@ -0,0 +1,168 @@
+#!/usr/bin/env python
+
+from __future__ import absolute_import, print_function
+
+import argparse
+import json
+import sys
+import time
+
+from . import DecodeError, __version__, decode, encode
+
+
+def encode_payload(args):
+ # Try to encode
+ if args.key is None:
+ raise ValueError('Key is required when encoding. See --help for usage.')
+
+ # Build payload object to encode
+ payload = {}
+
+ for arg in args.payload:
+ k, v = arg.split('=', 1)
+
+ # exp +offset special case?
+ if k == 'exp' and v[0] == '+' and len(v) > 1:
+ v = str(int(time.time()+int(v[1:])))
+
+ # Cast to integer?
+ if v.isdigit():
+ v = int(v)
+ else:
+ # Cast to float?
+ try:
+ v = float(v)
+ except ValueError:
+ pass
+
+ # Cast to true, false, or null?
+ constants = {'true': True, 'false': False, 'null': None}
+
+ if v in constants:
+ v = constants[v]
+
+ payload[k] = v
+
+ token = encode(
+ payload,
+ key=args.key,
+ algorithm=args.algorithm
+ )
+
+ return token.decode('utf-8')
+
+
+def decode_payload(args):
+ try:
+ if args.token:
+ token = args.token
+ else:
+ if sys.stdin.isatty():
+ token = sys.stdin.readline().strip()
+ else:
+ raise IOError('Cannot read from stdin: terminal not a TTY')
+
+ token = token.encode('utf-8')
+ data = decode(token, key=args.key, verify=args.verify)
+
+ return json.dumps(data)
+
+ except DecodeError as e:
+ raise DecodeError('There was an error decoding the token: %s' % e)
+
+
+def build_argparser():
+
+ usage = '''
+ Encodes or decodes JSON Web Tokens based on input.
+
+ %(prog)s [options] <command> [options] input
+
+ Decoding examples:
+
+ %(prog)s --key=secret decode json.web.token
+ %(prog)s decode --no-verify json.web.token
+
+ Encoding requires the key option and takes space separated key/value pairs
+ separated by equals (=) as input. Examples:
+
+ %(prog)s --key=secret encode iss=me exp=1302049071
+ %(prog)s --key=secret encode foo=bar exp=+10
+
+ The exp key is special and can take an offset to current Unix time.
+ '''
+
+ arg_parser = argparse.ArgumentParser(
+ prog='pyjwt',
+ usage=usage
+ )
+
+ arg_parser.add_argument(
+ '-v', '--version',
+ action='version',
+ version='%(prog)s ' + __version__
+ )
+
+ arg_parser.add_argument(
+ '--key',
+ dest='key',
+ metavar='KEY',
+ default=None,
+ help='set the secret key to sign with'
+ )
+
+ arg_parser.add_argument(
+ '--alg',
+ dest='algorithm',
+ metavar='ALG',
+ default='HS256',
+ help='set crypto algorithm to sign with. default=HS256'
+ )
+
+ subparsers = arg_parser.add_subparsers(
+ title='PyJWT subcommands',
+ description='valid subcommands',
+ help='additional help'
+ )
+
+ # Encode subcommand
+ encode_parser = subparsers.add_parser('encode', help='use to encode a supplied payload')
+
+ payload_help = """Payload to encode. Must be a space separated list of key/value
+ pairs separated by equals (=) sign."""
+
+ encode_parser.add_argument('payload', nargs='+', help=payload_help)
+ encode_parser.set_defaults(func=encode_payload)
+
+ # Decode subcommand
+ decode_parser = subparsers.add_parser('decode', help='use to decode a supplied JSON web token')
+ decode_parser.add_argument(
+ 'token',
+ help='JSON web token to decode.',
+ nargs='?')
+
+ decode_parser.add_argument(
+ '-n', '--no-verify',
+ action='store_false',
+ dest='verify',
+ default=True,
+ help='ignore signature and claims verification on decode'
+ )
+
+ decode_parser.set_defaults(func=decode_payload)
+
+ return arg_parser
+
+
+def main():
+ arg_parser = build_argparser()
+
+ try:
+ arguments = arg_parser.parse_args(sys.argv[1:])
+
+ output = arguments.func(arguments)
+
+ print(output)
+ except Exception as e:
+ print('There was an unforseen error: ', e)
+ arg_parser.print_help()
diff --git a/contrib/python/PyJWT/py2/jwt/algorithms.py b/contrib/python/PyJWT/py2/jwt/algorithms.py
new file mode 100644
index 00000000000..13436883417
--- /dev/null
+++ b/contrib/python/PyJWT/py2/jwt/algorithms.py
@@ -0,0 +1,403 @@
+import hashlib
+import hmac
+import json
+
+
+from .compat import constant_time_compare, string_types
+from .exceptions import InvalidKeyError
+from .utils import (
+ base64url_decode, base64url_encode, der_to_raw_signature,
+ force_bytes, force_unicode, from_base64url_uint, raw_to_der_signature,
+ to_base64url_uint
+)
+
+try:
+ from cryptography.hazmat.primitives import hashes
+ from cryptography.hazmat.primitives.serialization import (
+ load_pem_private_key, load_pem_public_key, load_ssh_public_key
+ )
+ from cryptography.hazmat.primitives.asymmetric.rsa import (
+ RSAPrivateKey, RSAPublicKey, RSAPrivateNumbers, RSAPublicNumbers,
+ rsa_recover_prime_factors, rsa_crt_dmp1, rsa_crt_dmq1, rsa_crt_iqmp
+ )
+ from cryptography.hazmat.primitives.asymmetric.ec import (
+ EllipticCurvePrivateKey, EllipticCurvePublicKey
+ )
+ from cryptography.hazmat.primitives.asymmetric import ec, padding
+ from cryptography.hazmat.backends import default_backend
+ from cryptography.exceptions import InvalidSignature
+
+ has_crypto = True
+except ImportError:
+ has_crypto = False
+
+requires_cryptography = set(['RS256', 'RS384', 'RS512', 'ES256', 'ES384',
+ 'ES521', 'ES512', 'PS256', 'PS384', 'PS512'])
+
+
+def get_default_algorithms():
+ """
+ Returns the algorithms that are implemented by the library.
+ """
+ default_algorithms = {
+ 'none': NoneAlgorithm(),
+ 'HS256': HMACAlgorithm(HMACAlgorithm.SHA256),
+ 'HS384': HMACAlgorithm(HMACAlgorithm.SHA384),
+ 'HS512': HMACAlgorithm(HMACAlgorithm.SHA512)
+ }
+
+ if has_crypto:
+ default_algorithms.update({
+ 'RS256': RSAAlgorithm(RSAAlgorithm.SHA256),
+ 'RS384': RSAAlgorithm(RSAAlgorithm.SHA384),
+ 'RS512': RSAAlgorithm(RSAAlgorithm.SHA512),
+ 'ES256': ECAlgorithm(ECAlgorithm.SHA256),
+ 'ES384': ECAlgorithm(ECAlgorithm.SHA384),
+ 'ES521': ECAlgorithm(ECAlgorithm.SHA512),
+ 'ES512': ECAlgorithm(ECAlgorithm.SHA512), # Backward compat for #219 fix
+ 'PS256': RSAPSSAlgorithm(RSAPSSAlgorithm.SHA256),
+ 'PS384': RSAPSSAlgorithm(RSAPSSAlgorithm.SHA384),
+ 'PS512': RSAPSSAlgorithm(RSAPSSAlgorithm.SHA512)
+ })
+
+ return default_algorithms
+
+
+class Algorithm(object):
+ """
+ The interface for an algorithm used to sign and verify tokens.
+ """
+ def prepare_key(self, key):
+ """
+ Performs necessary validation and conversions on the key and returns
+ the key value in the proper format for sign() and verify().
+ """
+ raise NotImplementedError
+
+ def sign(self, msg, key):
+ """
+ Returns a digital signature for the specified message
+ using the specified key value.
+ """
+ raise NotImplementedError
+
+ def verify(self, msg, key, sig):
+ """
+ Verifies that the specified digital signature is valid
+ for the specified message and key values.
+ """
+ raise NotImplementedError
+
+ @staticmethod
+ def to_jwk(key_obj):
+ """
+ Serializes a given RSA key into a JWK
+ """
+ raise NotImplementedError
+
+ @staticmethod
+ def from_jwk(jwk):
+ """
+ Deserializes a given RSA key from JWK back into a PublicKey or PrivateKey object
+ """
+ raise NotImplementedError
+
+
+class NoneAlgorithm(Algorithm):
+ """
+ Placeholder for use when no signing or verification
+ operations are required.
+ """
+ def prepare_key(self, key):
+ if key == '':
+ key = None
+
+ if key is not None:
+ raise InvalidKeyError('When alg = "none", key value must be None.')
+
+ return key
+
+ def sign(self, msg, key):
+ return b''
+
+ def verify(self, msg, key, sig):
+ return False
+
+
+class HMACAlgorithm(Algorithm):
+ """
+ Performs signing and verification operations using HMAC
+ and the specified hash function.
+ """
+ SHA256 = hashlib.sha256
+ SHA384 = hashlib.sha384
+ SHA512 = hashlib.sha512
+
+ def __init__(self, hash_alg):
+ self.hash_alg = hash_alg
+
+ def prepare_key(self, key):
+ key = force_bytes(key)
+
+ invalid_strings = [
+ b'-----BEGIN PUBLIC KEY-----',
+ b'-----BEGIN CERTIFICATE-----',
+ b'-----BEGIN RSA PUBLIC KEY-----',
+ b'ssh-rsa'
+ ]
+
+ if any([string_value in key for string_value in invalid_strings]):
+ raise InvalidKeyError(
+ 'The specified key is an asymmetric key or x509 certificate and'
+ ' should not be used as an HMAC secret.')
+
+ return key
+
+ @staticmethod
+ def to_jwk(key_obj):
+ return json.dumps({
+ 'k': force_unicode(base64url_encode(force_bytes(key_obj))),
+ 'kty': 'oct'
+ })
+
+ @staticmethod
+ def from_jwk(jwk):
+ obj = json.loads(jwk)
+
+ if obj.get('kty') != 'oct':
+ raise InvalidKeyError('Not an HMAC key')
+
+ return base64url_decode(obj['k'])
+
+ def sign(self, msg, key):
+ return hmac.new(key, msg, self.hash_alg).digest()
+
+ def verify(self, msg, key, sig):
+ return constant_time_compare(sig, self.sign(msg, key))
+
+
+if has_crypto:
+
+ class RSAAlgorithm(Algorithm):
+ """
+ Performs signing and verification operations using
+ RSASSA-PKCS-v1_5 and the specified hash function.
+ """
+ SHA256 = hashes.SHA256
+ SHA384 = hashes.SHA384
+ SHA512 = hashes.SHA512
+
+ def __init__(self, hash_alg):
+ self.hash_alg = hash_alg
+
+ def prepare_key(self, key):
+ if isinstance(key, RSAPrivateKey) or \
+ isinstance(key, RSAPublicKey):
+ return key
+
+ if isinstance(key, string_types):
+ key = force_bytes(key)
+
+ try:
+ if key.startswith(b'ssh-rsa'):
+ key = load_ssh_public_key(key, backend=default_backend())
+ else:
+ key = load_pem_private_key(key, password=None, backend=default_backend())
+ except ValueError:
+ key = load_pem_public_key(key, backend=default_backend())
+ else:
+ raise TypeError('Expecting a PEM-formatted key.')
+
+ return key
+
+ @staticmethod
+ def to_jwk(key_obj):
+ obj = None
+
+ if getattr(key_obj, 'private_numbers', None):
+ # Private key
+ numbers = key_obj.private_numbers()
+
+ obj = {
+ 'kty': 'RSA',
+ 'key_ops': ['sign'],
+ 'n': force_unicode(to_base64url_uint(numbers.public_numbers.n)),
+ 'e': force_unicode(to_base64url_uint(numbers.public_numbers.e)),
+ 'd': force_unicode(to_base64url_uint(numbers.d)),
+ 'p': force_unicode(to_base64url_uint(numbers.p)),
+ 'q': force_unicode(to_base64url_uint(numbers.q)),
+ 'dp': force_unicode(to_base64url_uint(numbers.dmp1)),
+ 'dq': force_unicode(to_base64url_uint(numbers.dmq1)),
+ 'qi': force_unicode(to_base64url_uint(numbers.iqmp))
+ }
+
+ elif getattr(key_obj, 'verify', None):
+ # Public key
+ numbers = key_obj.public_numbers()
+
+ obj = {
+ 'kty': 'RSA',
+ 'key_ops': ['verify'],
+ 'n': force_unicode(to_base64url_uint(numbers.n)),
+ 'e': force_unicode(to_base64url_uint(numbers.e))
+ }
+ else:
+ raise InvalidKeyError('Not a public or private key')
+
+ return json.dumps(obj)
+
+ @staticmethod
+ def from_jwk(jwk):
+ try:
+ obj = json.loads(jwk)
+ except ValueError:
+ raise InvalidKeyError('Key is not valid JSON')
+
+ if obj.get('kty') != 'RSA':
+ raise InvalidKeyError('Not an RSA key')
+
+ if 'd' in obj and 'e' in obj and 'n' in obj:
+ # Private key
+ if 'oth' in obj:
+ raise InvalidKeyError('Unsupported RSA private key: > 2 primes not supported')
+
+ other_props = ['p', 'q', 'dp', 'dq', 'qi']
+ props_found = [prop in obj for prop in other_props]
+ any_props_found = any(props_found)
+
+ if any_props_found and not all(props_found):
+ raise InvalidKeyError('RSA key must include all parameters if any are present besides d')
+
+ public_numbers = RSAPublicNumbers(
+ from_base64url_uint(obj['e']), from_base64url_uint(obj['n'])
+ )
+
+ if any_props_found:
+ numbers = RSAPrivateNumbers(
+ d=from_base64url_uint(obj['d']),
+ p=from_base64url_uint(obj['p']),
+ q=from_base64url_uint(obj['q']),
+ dmp1=from_base64url_uint(obj['dp']),
+ dmq1=from_base64url_uint(obj['dq']),
+ iqmp=from_base64url_uint(obj['qi']),
+ public_numbers=public_numbers
+ )
+ else:
+ d = from_base64url_uint(obj['d'])
+ p, q = rsa_recover_prime_factors(
+ public_numbers.n, d, public_numbers.e
+ )
+
+ numbers = RSAPrivateNumbers(
+ d=d,
+ p=p,
+ q=q,
+ dmp1=rsa_crt_dmp1(d, p),
+ dmq1=rsa_crt_dmq1(d, q),
+ iqmp=rsa_crt_iqmp(p, q),
+ public_numbers=public_numbers
+ )
+
+ return numbers.private_key(default_backend())
+ elif 'n' in obj and 'e' in obj:
+ # Public key
+ numbers = RSAPublicNumbers(
+ from_base64url_uint(obj['e']), from_base64url_uint(obj['n'])
+ )
+
+ return numbers.public_key(default_backend())
+ else:
+ raise InvalidKeyError('Not a public or private key')
+
+ def sign(self, msg, key):
+ return key.sign(msg, padding.PKCS1v15(), self.hash_alg())
+
+ def verify(self, msg, key, sig):
+ try:
+ key.verify(sig, msg, padding.PKCS1v15(), self.hash_alg())
+ return True
+ except InvalidSignature:
+ return False
+
+ class ECAlgorithm(Algorithm):
+ """
+ Performs signing and verification operations using
+ ECDSA and the specified hash function
+ """
+ SHA256 = hashes.SHA256
+ SHA384 = hashes.SHA384
+ SHA512 = hashes.SHA512
+
+ def __init__(self, hash_alg):
+ self.hash_alg = hash_alg
+
+ def prepare_key(self, key):
+ if isinstance(key, EllipticCurvePrivateKey) or \
+ isinstance(key, EllipticCurvePublicKey):
+ return key
+
+ if isinstance(key, string_types):
+ key = force_bytes(key)
+
+ # Attempt to load key. We don't know if it's
+ # a Signing Key or a Verifying Key, so we try
+ # the Verifying Key first.
+ try:
+ if key.startswith(b'ecdsa-sha2-'):
+ key = load_ssh_public_key(key, backend=default_backend())
+ else:
+ key = load_pem_public_key(key, backend=default_backend())
+ except ValueError:
+ key = load_pem_private_key(key, password=None, backend=default_backend())
+
+ else:
+ raise TypeError('Expecting a PEM-formatted key.')
+
+ return key
+
+ def sign(self, msg, key):
+ der_sig = key.sign(msg, ec.ECDSA(self.hash_alg()))
+
+ return der_to_raw_signature(der_sig, key.curve)
+
+ def verify(self, msg, key, sig):
+ try:
+ der_sig = raw_to_der_signature(sig, key.curve)
+ except ValueError:
+ return False
+
+ try:
+ key.verify(der_sig, msg, ec.ECDSA(self.hash_alg()))
+ return True
+ except InvalidSignature:
+ return False
+
+ class RSAPSSAlgorithm(RSAAlgorithm):
+ """
+ Performs a signature using RSASSA-PSS with MGF1
+ """
+
+ def sign(self, msg, key):
+ return key.sign(
+ msg,
+ padding.PSS(
+ mgf=padding.MGF1(self.hash_alg()),
+ salt_length=self.hash_alg.digest_size
+ ),
+ self.hash_alg()
+ )
+
+ def verify(self, msg, key, sig):
+ try:
+ key.verify(
+ sig,
+ msg,
+ padding.PSS(
+ mgf=padding.MGF1(self.hash_alg()),
+ salt_length=self.hash_alg.digest_size
+ ),
+ self.hash_alg()
+ )
+ return True
+ except InvalidSignature:
+ return False
diff --git a/contrib/python/PyJWT/py2/jwt/api_jws.py b/contrib/python/PyJWT/py2/jwt/api_jws.py
new file mode 100644
index 00000000000..a9354adb06c
--- /dev/null
+++ b/contrib/python/PyJWT/py2/jwt/api_jws.py
@@ -0,0 +1,242 @@
+import binascii
+import json
+import warnings
+try:
+ # import required by mypy to perform type checking, not used for normal execution
+ from typing import Callable, Dict, List, Optional, Union # NOQA
+except ImportError:
+ pass
+
+from .algorithms import (
+ Algorithm, get_default_algorithms, has_crypto, requires_cryptography # NOQA
+)
+from .compat import Mapping, binary_type, string_types, text_type
+from .exceptions import (
+ DecodeError, InvalidAlgorithmError, InvalidSignatureError,
+ InvalidTokenError
+)
+from .utils import base64url_decode, base64url_encode, force_bytes, merge_dict
+
+
+class PyJWS(object):
+ header_typ = 'JWT'
+
+ def __init__(self, algorithms=None, options=None):
+ self._algorithms = get_default_algorithms()
+ self._valid_algs = (set(algorithms) if algorithms is not None
+ else set(self._algorithms))
+
+ # Remove algorithms that aren't on the whitelist
+ for key in list(self._algorithms.keys()):
+ if key not in self._valid_algs:
+ del self._algorithms[key]
+
+ if not options:
+ options = {}
+
+ self.options = merge_dict(self._get_default_options(), options)
+
+ @staticmethod
+ def _get_default_options():
+ return {
+ 'verify_signature': True
+ }
+
+ def register_algorithm(self, alg_id, alg_obj):
+ """
+ Registers a new Algorithm for use when creating and verifying tokens.
+ """
+ if alg_id in self._algorithms:
+ raise ValueError('Algorithm already has a handler.')
+
+ if not isinstance(alg_obj, Algorithm):
+ raise TypeError('Object is not of type `Algorithm`')
+
+ self._algorithms[alg_id] = alg_obj
+ self._valid_algs.add(alg_id)
+
+ def unregister_algorithm(self, alg_id):
+ """
+ Unregisters an Algorithm for use when creating and verifying tokens
+ Throws KeyError if algorithm is not registered.
+ """
+ if alg_id not in self._algorithms:
+ raise KeyError('The specified algorithm could not be removed'
+ ' because it is not registered.')
+
+ del self._algorithms[alg_id]
+ self._valid_algs.remove(alg_id)
+
+ def get_algorithms(self):
+ """
+ Returns a list of supported values for the 'alg' parameter.
+ """
+ return list(self._valid_algs)
+
+ def encode(self,
+ payload, # type: Union[Dict, bytes]
+ key, # type: str
+ algorithm='HS256', # type: str
+ headers=None, # type: Optional[Dict]
+ json_encoder=None # type: Optional[Callable]
+ ):
+ segments = []
+
+ if algorithm is None:
+ algorithm = 'none'
+
+ if algorithm not in self._valid_algs:
+ pass
+
+ # Header
+ header = {'typ': self.header_typ, 'alg': algorithm}
+
+ if headers:
+ self._validate_headers(headers)
+ header.update(headers)
+
+ json_header = force_bytes(
+ json.dumps(
+ header,
+ separators=(',', ':'),
+ cls=json_encoder
+ )
+ )
+
+ segments.append(base64url_encode(json_header))
+ segments.append(base64url_encode(payload))
+
+ # Segments
+ signing_input = b'.'.join(segments)
+ try:
+ alg_obj = self._algorithms[algorithm]
+ key = alg_obj.prepare_key(key)
+ signature = alg_obj.sign(signing_input, key)
+
+ except KeyError:
+ if not has_crypto and algorithm in requires_cryptography:
+ raise NotImplementedError(
+ "Algorithm '%s' could not be found. Do you have cryptography "
+ "installed?" % algorithm
+ )
+ else:
+ raise NotImplementedError('Algorithm not supported')
+
+ segments.append(base64url_encode(signature))
+
+ return b'.'.join(segments)
+
+ def decode(self,
+ jwt, # type: str
+ key='', # type: str
+ verify=True, # type: bool
+ algorithms=None, # type: List[str]
+ options=None, # type: Dict
+ **kwargs):
+
+ merged_options = merge_dict(self.options, options)
+ verify_signature = merged_options['verify_signature']
+
+ if verify_signature and not algorithms:
+ warnings.warn(
+ 'It is strongly recommended that you pass in a ' +
+ 'value for the "algorithms" argument when calling decode(). ' +
+ 'This argument will be mandatory in a future version.',
+ DeprecationWarning
+ )
+
+ payload, signing_input, header, signature = self._load(jwt)
+
+ if not verify:
+ warnings.warn('The verify parameter is deprecated. '
+ 'Please use verify_signature in options instead.',
+ DeprecationWarning, stacklevel=2)
+ elif verify_signature:
+ self._verify_signature(payload, signing_input, header, signature,
+ key, algorithms)
+
+ return payload
+
+ def get_unverified_header(self, jwt):
+ """Returns back the JWT header parameters as a dict()
+
+ Note: The signature is not verified so the header parameters
+ should not be fully trusted until signature verification is complete
+ """
+ headers = self._load(jwt)[2]
+ self._validate_headers(headers)
+
+ return headers
+
+ def _load(self, jwt):
+ if isinstance(jwt, text_type):
+ jwt = jwt.encode('utf-8')
+
+ if not issubclass(type(jwt), binary_type):
+ raise DecodeError("Invalid token type. Token must be a {0}".format(
+ binary_type))
+
+ try:
+ signing_input, crypto_segment = jwt.rsplit(b'.', 1)
+ header_segment, payload_segment = signing_input.split(b'.', 1)
+ except ValueError:
+ raise DecodeError('Not enough segments')
+
+ try:
+ header_data = base64url_decode(header_segment)
+ except (TypeError, binascii.Error):
+ raise DecodeError('Invalid header padding')
+
+ try:
+ header = json.loads(header_data.decode('utf-8'))
+ except ValueError as e:
+ raise DecodeError('Invalid header string: %s' % e)
+
+ if not isinstance(header, Mapping):
+ raise DecodeError('Invalid header string: must be a json object')
+
+ try:
+ payload = base64url_decode(payload_segment)
+ except (TypeError, binascii.Error):
+ raise DecodeError('Invalid payload padding')
+
+ try:
+ signature = base64url_decode(crypto_segment)
+ except (TypeError, binascii.Error):
+ raise DecodeError('Invalid crypto padding')
+
+ return (payload, signing_input, header, signature)
+
+ def _verify_signature(self, payload, signing_input, header, signature,
+ key='', algorithms=None):
+
+ alg = header.get('alg')
+
+ if algorithms is not None and alg not in algorithms:
+ raise InvalidAlgorithmError('The specified alg value is not allowed')
+
+ try:
+ alg_obj = self._algorithms[alg]
+ key = alg_obj.prepare_key(key)
+
+ if not alg_obj.verify(signing_input, key, signature):
+ raise InvalidSignatureError('Signature verification failed')
+
+ except KeyError:
+ raise InvalidAlgorithmError('Algorithm not supported')
+
+ def _validate_headers(self, headers):
+ if 'kid' in headers:
+ self._validate_kid(headers['kid'])
+
+ def _validate_kid(self, kid):
+ if not isinstance(kid, string_types):
+ raise InvalidTokenError('Key ID header parameter must be a string')
+
+
+_jws_global_obj = PyJWS()
+encode = _jws_global_obj.encode
+decode = _jws_global_obj.decode
+register_algorithm = _jws_global_obj.register_algorithm
+unregister_algorithm = _jws_global_obj.unregister_algorithm
+get_unverified_header = _jws_global_obj.get_unverified_header
diff --git a/contrib/python/PyJWT/py2/jwt/api_jwt.py b/contrib/python/PyJWT/py2/jwt/api_jwt.py
new file mode 100644
index 00000000000..85504acf930
--- /dev/null
+++ b/contrib/python/PyJWT/py2/jwt/api_jwt.py
@@ -0,0 +1,222 @@
+import json
+import warnings
+from calendar import timegm
+from datetime import datetime, timedelta
+try:
+ # import required by mypy to perform type checking, not used for normal execution
+ from typing import Callable, Dict, List, Optional, Union # NOQA
+except ImportError:
+ pass
+
+from .api_jws import PyJWS
+from .algorithms import Algorithm, get_default_algorithms # NOQA
+from .compat import Iterable, Mapping, string_types
+from .exceptions import (
+ DecodeError, ExpiredSignatureError, ImmatureSignatureError,
+ InvalidAudienceError, InvalidIssuedAtError,
+ InvalidIssuerError, MissingRequiredClaimError
+)
+from .utils import merge_dict
+
+
+class PyJWT(PyJWS):
+ header_type = 'JWT'
+
+ @staticmethod
+ def _get_default_options():
+ # type: () -> Dict[str, bool]
+ return {
+ 'verify_signature': True,
+ 'verify_exp': True,
+ 'verify_nbf': True,
+ 'verify_iat': True,
+ 'verify_aud': True,
+ 'verify_iss': True,
+ 'require_exp': False,
+ 'require_iat': False,
+ 'require_nbf': False
+ }
+
+ def encode(self,
+ payload, # type: Union[Dict, bytes]
+ key, # type: str
+ algorithm='HS256', # type: str
+ headers=None, # type: Optional[Dict]
+ json_encoder=None # type: Optional[Callable]
+ ):
+ # Check that we get a mapping
+ if not isinstance(payload, Mapping):
+ raise TypeError('Expecting a mapping object, as JWT only supports '
+ 'JSON objects as payloads.')
+
+ # Payload
+ for time_claim in ['exp', 'iat', 'nbf']:
+ # Convert datetime to a intDate value in known time-format claims
+ if isinstance(payload.get(time_claim), datetime):
+ payload[time_claim] = timegm(payload[time_claim].utctimetuple()) # type: ignore
+
+ json_payload = json.dumps(
+ payload,
+ separators=(',', ':'),
+ cls=json_encoder
+ ).encode('utf-8')
+
+ return super(PyJWT, self).encode(
+ json_payload, key, algorithm, headers, json_encoder
+ )
+
+ def decode(self,
+ jwt, # type: str
+ key='', # type: str
+ verify=True, # type: bool
+ algorithms=None, # type: List[str]
+ options=None, # type: Dict
+ **kwargs):
+
+ if verify and not algorithms:
+ warnings.warn(
+ 'It is strongly recommended that you pass in a ' +
+ 'value for the "algorithms" argument when calling decode(). ' +
+ 'This argument will be mandatory in a future version.',
+ DeprecationWarning
+ )
+
+ payload, _, _, _ = self._load(jwt)
+
+ if options is None:
+ options = {'verify_signature': verify}
+ else:
+ options.setdefault('verify_signature', verify)
+
+ decoded = super(PyJWT, self).decode(
+ jwt, key=key, algorithms=algorithms, options=options, **kwargs
+ )
+
+ try:
+ payload = json.loads(decoded.decode('utf-8'))
+ except ValueError as e:
+ raise DecodeError('Invalid payload string: %s' % e)
+ if not isinstance(payload, Mapping):
+ raise DecodeError('Invalid payload string: must be a json object')
+
+ if verify:
+ merged_options = merge_dict(self.options, options)
+ self._validate_claims(payload, merged_options, **kwargs)
+
+ return payload
+
+ def _validate_claims(self, payload, options, audience=None, issuer=None,
+ leeway=0, **kwargs):
+
+ if 'verify_expiration' in kwargs:
+ options['verify_exp'] = kwargs.get('verify_expiration', True)
+ warnings.warn('The verify_expiration parameter is deprecated. '
+ 'Please use verify_exp in options instead.',
+ DeprecationWarning)
+
+ if isinstance(leeway, timedelta):
+ leeway = leeway.total_seconds()
+
+ if not isinstance(audience, (string_types, type(None), Iterable)):
+ raise TypeError('audience must be a string, iterable, or None')
+
+ self._validate_required_claims(payload, options)
+
+ now = timegm(datetime.utcnow().utctimetuple())
+
+ if 'iat' in payload and options.get('verify_iat'):
+ self._validate_iat(payload, now, leeway)
+
+ if 'nbf' in payload and options.get('verify_nbf'):
+ self._validate_nbf(payload, now, leeway)
+
+ if 'exp' in payload and options.get('verify_exp'):
+ self._validate_exp(payload, now, leeway)
+
+ if options.get('verify_iss'):
+ self._validate_iss(payload, issuer)
+
+ if options.get('verify_aud'):
+ self._validate_aud(payload, audience)
+
+ def _validate_required_claims(self, payload, options):
+ if options.get('require_exp') and payload.get('exp') is None:
+ raise MissingRequiredClaimError('exp')
+
+ if options.get('require_iat') and payload.get('iat') is None:
+ raise MissingRequiredClaimError('iat')
+
+ if options.get('require_nbf') and payload.get('nbf') is None:
+ raise MissingRequiredClaimError('nbf')
+
+ def _validate_iat(self, payload, now, leeway):
+ try:
+ int(payload['iat'])
+ except ValueError:
+ raise InvalidIssuedAtError('Issued At claim (iat) must be an integer.')
+
+ def _validate_nbf(self, payload, now, leeway):
+ try:
+ nbf = int(payload['nbf'])
+ except ValueError:
+ raise DecodeError('Not Before claim (nbf) must be an integer.')
+
+ if nbf > (now + leeway):
+ raise ImmatureSignatureError('The token is not yet valid (nbf)')
+
+ def _validate_exp(self, payload, now, leeway):
+ try:
+ exp = int(payload['exp'])
+ except ValueError:
+ raise DecodeError('Expiration Time claim (exp) must be an'
+ ' integer.')
+
+ if exp < (now - leeway):
+ raise ExpiredSignatureError('Signature has expired')
+
+ def _validate_aud(self, payload, audience):
+ if audience is None and 'aud' not in payload:
+ return
+
+ if audience is not None and 'aud' not in payload:
+ # Application specified an audience, but it could not be
+ # verified since the token does not contain a claim.
+ raise MissingRequiredClaimError('aud')
+
+ if audience is None and 'aud' in payload:
+ # Application did not specify an audience, but
+ # the token has the 'aud' claim
+ raise InvalidAudienceError('Invalid audience')
+
+ audience_claims = payload['aud']
+
+ if isinstance(audience_claims, string_types):
+ audience_claims = [audience_claims]
+ if not isinstance(audience_claims, list):
+ raise InvalidAudienceError('Invalid claim format in token')
+ if any(not isinstance(c, string_types) for c in audience_claims):
+ raise InvalidAudienceError('Invalid claim format in token')
+
+ if isinstance(audience, string_types):
+ audience = [audience]
+
+ if not any(aud in audience_claims for aud in audience):
+ raise InvalidAudienceError('Invalid audience')
+
+ def _validate_iss(self, payload, issuer):
+ if issuer is None:
+ return
+
+ if 'iss' not in payload:
+ raise MissingRequiredClaimError('iss')
+
+ if payload['iss'] != issuer:
+ raise InvalidIssuerError('Invalid issuer')
+
+
+_jwt_global_obj = PyJWT()
+encode = _jwt_global_obj.encode
+decode = _jwt_global_obj.decode
+register_algorithm = _jwt_global_obj.register_algorithm
+unregister_algorithm = _jwt_global_obj.unregister_algorithm
+get_unverified_header = _jwt_global_obj.get_unverified_header
diff --git a/contrib/python/PyJWT/py2/jwt/compat.py b/contrib/python/PyJWT/py2/jwt/compat.py
new file mode 100644
index 00000000000..e79e258e56d
--- /dev/null
+++ b/contrib/python/PyJWT/py2/jwt/compat.py
@@ -0,0 +1,68 @@
+"""
+The `compat` module provides support for backwards compatibility with older
+versions of python, and compatibility wrappers around optional packages.
+"""
+# flake8: noqa
+import hmac
+import struct
+import sys
+
+
+PY3 = sys.version_info[0] == 3
+
+
+if PY3:
+ text_type = str
+ binary_type = bytes
+else:
+ text_type = unicode
+ binary_type = str
+
+string_types = (text_type, binary_type)
+
+try:
+ # Importing ABCs from collections will be removed in PY3.8
+ from collections.abc import Iterable, Mapping
+except ImportError:
+ from collections import Iterable, Mapping
+
+try:
+ constant_time_compare = hmac.compare_digest
+except AttributeError:
+ # Fallback for Python < 2.7
+ def constant_time_compare(val1, val2):
+ """
+ Returns True if the two strings are equal, False otherwise.
+
+ The time taken is independent of the number of characters that match.
+ """
+ if len(val1) != len(val2):
+ return False
+
+ result = 0
+
+ for x, y in zip(val1, val2):
+ result |= ord(x) ^ ord(y)
+
+ return result == 0
+
+# Use int.to_bytes if it exists (Python 3)
+if getattr(int, 'to_bytes', None):
+ def bytes_from_int(val):
+ remaining = val
+ byte_length = 0
+
+ while remaining != 0:
+ remaining = remaining >> 8
+ byte_length += 1
+
+ return val.to_bytes(byte_length, 'big', signed=False)
+else:
+ def bytes_from_int(val):
+ buf = []
+ while val:
+ val, remainder = divmod(val, 256)
+ buf.append(remainder)
+
+ buf.reverse()
+ return struct.pack('%sB' % len(buf), *buf)
diff --git a/contrib/python/tzlocal/py3/tzlocal/py.typed b/contrib/python/PyJWT/py2/jwt/contrib/__init__.py
index e69de29bb2d..e69de29bb2d 100644
--- a/contrib/python/tzlocal/py3/tzlocal/py.typed
+++ b/contrib/python/PyJWT/py2/jwt/contrib/__init__.py
diff --git a/contrib/python/PyJWT/py2/jwt/contrib/algorithms/__init__.py b/contrib/python/PyJWT/py2/jwt/contrib/algorithms/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/contrib/python/PyJWT/py2/jwt/contrib/algorithms/__init__.py
diff --git a/contrib/python/PyJWT/py2/jwt/contrib/algorithms/py_ecdsa.py b/contrib/python/PyJWT/py2/jwt/contrib/algorithms/py_ecdsa.py
new file mode 100644
index 00000000000..bf0dea5ae28
--- /dev/null
+++ b/contrib/python/PyJWT/py2/jwt/contrib/algorithms/py_ecdsa.py
@@ -0,0 +1,60 @@
+# Note: This file is named py_ecdsa.py because import behavior in Python 2
+# would cause ecdsa.py to squash the ecdsa library that it depends upon.
+
+import hashlib
+
+import ecdsa
+
+from jwt.algorithms import Algorithm
+from jwt.compat import string_types, text_type
+
+
+class ECAlgorithm(Algorithm):
+ """
+ Performs signing and verification operations using
+ ECDSA and the specified hash function
+
+ This class requires the ecdsa package to be installed.
+
+ This is based off of the implementation in PyJWT 0.3.2
+ """
+ SHA256 = hashlib.sha256
+ SHA384 = hashlib.sha384
+ SHA512 = hashlib.sha512
+
+ def __init__(self, hash_alg):
+ self.hash_alg = hash_alg
+
+ def prepare_key(self, key):
+
+ if isinstance(key, ecdsa.SigningKey) or \
+ isinstance(key, ecdsa.VerifyingKey):
+ return key
+
+ if isinstance(key, string_types):
+ if isinstance(key, text_type):
+ key = key.encode('utf-8')
+
+ # Attempt to load key. We don't know if it's
+ # a Signing Key or a Verifying Key, so we try
+ # the Verifying Key first.
+ try:
+ key = ecdsa.VerifyingKey.from_pem(key)
+ except ecdsa.der.UnexpectedDER:
+ key = ecdsa.SigningKey.from_pem(key)
+
+ else:
+ raise TypeError('Expecting a PEM-formatted key.')
+
+ return key
+
+ def sign(self, msg, key):
+ return key.sign(msg, hashfunc=self.hash_alg,
+ sigencode=ecdsa.util.sigencode_string)
+
+ def verify(self, msg, key, sig):
+ try:
+ return key.verify(sig, msg, hashfunc=self.hash_alg,
+ sigdecode=ecdsa.util.sigdecode_string)
+ except AssertionError:
+ return False
diff --git a/contrib/python/PyJWT/py2/jwt/contrib/algorithms/pycrypto.py b/contrib/python/PyJWT/py2/jwt/contrib/algorithms/pycrypto.py
new file mode 100644
index 00000000000..e49cdbfe40f
--- /dev/null
+++ b/contrib/python/PyJWT/py2/jwt/contrib/algorithms/pycrypto.py
@@ -0,0 +1,46 @@
+import Crypto.Hash.SHA256
+import Crypto.Hash.SHA384
+import Crypto.Hash.SHA512
+from Crypto.PublicKey import RSA
+from Crypto.Signature import PKCS1_v1_5
+
+from jwt.algorithms import Algorithm
+from jwt.compat import string_types, text_type
+
+
+class RSAAlgorithm(Algorithm):
+ """
+ Performs signing and verification operations using
+ RSASSA-PKCS-v1_5 and the specified hash function.
+
+ This class requires PyCrypto package to be installed.
+
+ This is based off of the implementation in PyJWT 0.3.2
+ """
+ SHA256 = Crypto.Hash.SHA256
+ SHA384 = Crypto.Hash.SHA384
+ SHA512 = Crypto.Hash.SHA512
+
+ def __init__(self, hash_alg):
+ self.hash_alg = hash_alg
+
+ def prepare_key(self, key):
+
+ if isinstance(key, RSA._RSAobj):
+ return key
+
+ if isinstance(key, string_types):
+ if isinstance(key, text_type):
+ key = key.encode('utf-8')
+
+ key = RSA.importKey(key)
+ else:
+ raise TypeError('Expecting a PEM- or RSA-formatted key.')
+
+ return key
+
+ def sign(self, msg, key):
+ return PKCS1_v1_5.new(key).sign(self.hash_alg.new(msg))
+
+ def verify(self, msg, key, sig):
+ return PKCS1_v1_5.new(key).verify(self.hash_alg.new(msg), sig)
diff --git a/contrib/python/PyJWT/py2/jwt/exceptions.py b/contrib/python/PyJWT/py2/jwt/exceptions.py
new file mode 100644
index 00000000000..2a6aa596ba0
--- /dev/null
+++ b/contrib/python/PyJWT/py2/jwt/exceptions.py
@@ -0,0 +1,59 @@
+class PyJWTError(Exception):
+ """
+ Base class for all exceptions
+ """
+ pass
+
+
+class InvalidTokenError(PyJWTError):
+ pass
+
+
+class DecodeError(InvalidTokenError):
+ pass
+
+
+class InvalidSignatureError(DecodeError):
+ pass
+
+
+class ExpiredSignatureError(InvalidTokenError):
+ pass
+
+
+class InvalidAudienceError(InvalidTokenError):
+ pass
+
+
+class InvalidIssuerError(InvalidTokenError):
+ pass
+
+
+class InvalidIssuedAtError(InvalidTokenError):
+ pass
+
+
+class ImmatureSignatureError(InvalidTokenError):
+ pass
+
+
+class InvalidKeyError(PyJWTError):
+ pass
+
+
+class InvalidAlgorithmError(InvalidTokenError):
+ pass
+
+
+class MissingRequiredClaimError(InvalidTokenError):
+ def __init__(self, claim):
+ self.claim = claim
+
+ def __str__(self):
+ return 'Token is missing the "%s" claim' % self.claim
+
+
+# Compatibility aliases (deprecated)
+ExpiredSignature = ExpiredSignatureError
+InvalidAudience = InvalidAudienceError
+InvalidIssuer = InvalidIssuerError
diff --git a/contrib/python/PyJWT/py2/jwt/help.py b/contrib/python/PyJWT/py2/jwt/help.py
new file mode 100644
index 00000000000..55e39ebb271
--- /dev/null
+++ b/contrib/python/PyJWT/py2/jwt/help.py
@@ -0,0 +1,61 @@
+from __future__ import print_function
+
+import json
+import platform
+import sys
+
+from . import __version__ as pyjwt_version
+
+try:
+ import cryptography
+except ImportError:
+ cryptography = None
+
+try:
+ import ecdsa
+except ImportError:
+ ecdsa = None
+
+
+def info():
+ """
+ Generate information for a bug report.
+ Based on the requests package help utility module.
+ """
+ try:
+ platform_info = {"system": platform.system(), "release": platform.release()}
+ except IOError:
+ platform_info = {"system": "Unknown", "release": "Unknown"}
+
+ implementation = platform.python_implementation()
+
+ if implementation == "CPython":
+ implementation_version = platform.python_version()
+ elif implementation == "PyPy":
+ implementation_version = "%s.%s.%s" % (
+ sys.pypy_version_info.major,
+ sys.pypy_version_info.minor,
+ sys.pypy_version_info.micro,
+ )
+ if sys.pypy_version_info.releaselevel != "final":
+ implementation_version = "".join(
+ [implementation_version, sys.pypy_version_info.releaselevel]
+ )
+ else:
+ implementation_version = "Unknown"
+
+ return {
+ "platform": platform_info,
+ "implementation": {"name": implementation, "version": implementation_version},
+ "cryptography": {"version": getattr(cryptography, "__version__", "")},
+ "pyjwt": {"version": pyjwt_version},
+ }
+
+
+def main():
+ """Pretty-print the bug information as JSON."""
+ print(json.dumps(info(), sort_keys=True, indent=2))
+
+
+if __name__ == "__main__":
+ main()
diff --git a/contrib/python/PyJWT/py2/jwt/utils.py b/contrib/python/PyJWT/py2/jwt/utils.py
new file mode 100644
index 00000000000..b33c7a2d455
--- /dev/null
+++ b/contrib/python/PyJWT/py2/jwt/utils.py
@@ -0,0 +1,113 @@
+import base64
+import binascii
+import struct
+
+from .compat import binary_type, bytes_from_int, text_type
+
+try:
+ from cryptography.hazmat.primitives.asymmetric.utils import (
+ decode_dss_signature, encode_dss_signature
+ )
+except ImportError:
+ pass
+
+
+def force_unicode(value):
+ if isinstance(value, binary_type):
+ return value.decode('utf-8')
+ elif isinstance(value, text_type):
+ return value
+ else:
+ raise TypeError('Expected a string value')
+
+
+def force_bytes(value):
+ if isinstance(value, text_type):
+ return value.encode('utf-8')
+ elif isinstance(value, binary_type):
+ return value
+ else:
+ raise TypeError('Expected a string value')
+
+
+def base64url_decode(input):
+ if isinstance(input, text_type):
+ input = input.encode('ascii')
+
+ rem = len(input) % 4
+
+ if rem > 0:
+ input += b'=' * (4 - rem)
+
+ return base64.urlsafe_b64decode(input)
+
+
+def base64url_encode(input):
+ return base64.urlsafe_b64encode(input).replace(b'=', b'')
+
+
+def to_base64url_uint(val):
+ if val < 0:
+ raise ValueError('Must be a positive integer')
+
+ int_bytes = bytes_from_int(val)
+
+ if len(int_bytes) == 0:
+ int_bytes = b'\x00'
+
+ return base64url_encode(int_bytes)
+
+
+def from_base64url_uint(val):
+ if isinstance(val, text_type):
+ val = val.encode('ascii')
+
+ data = base64url_decode(val)
+
+ buf = struct.unpack('%sB' % len(data), data)
+ return int(''.join(["%02x" % byte for byte in buf]), 16)
+
+
+def merge_dict(original, updates):
+ if not updates:
+ return original
+
+ try:
+ merged_options = original.copy()
+ merged_options.update(updates)
+ except (AttributeError, ValueError) as e:
+ raise TypeError('original and updates must be a dictionary: %s' % e)
+
+ return merged_options
+
+
+def number_to_bytes(num, num_bytes):
+ padded_hex = '%0*x' % (2 * num_bytes, num)
+ big_endian = binascii.a2b_hex(padded_hex.encode('ascii'))
+ return big_endian
+
+
+def bytes_to_number(string):
+ return int(binascii.b2a_hex(string), 16)
+
+
+def der_to_raw_signature(der_sig, curve):
+ num_bits = curve.key_size
+ num_bytes = (num_bits + 7) // 8
+
+ r, s = decode_dss_signature(der_sig)
+
+ return number_to_bytes(r, num_bytes) + number_to_bytes(s, num_bytes)
+
+
+def raw_to_der_signature(raw_sig, curve):
+ num_bits = curve.key_size
+ num_bytes = (num_bits + 7) // 8
+
+ if len(raw_sig) != 2 * num_bytes:
+ raise ValueError('Invalid signature')
+
+ r = bytes_to_number(raw_sig[:num_bytes])
+ s = bytes_to_number(raw_sig[num_bytes:])
+
+ return encode_dss_signature(r, s)
diff --git a/contrib/python/PyJWT/py2/ya.make b/contrib/python/PyJWT/py2/ya.make
new file mode 100644
index 00000000000..57a9352fba1
--- /dev/null
+++ b/contrib/python/PyJWT/py2/ya.make
@@ -0,0 +1,43 @@
+# Generated by devtools/yamaker (pypi).
+
+PY2_LIBRARY()
+
+VERSION(1.7.1)
+
+LICENSE(MIT)
+
+PEERDIR(
+ contrib/python/cryptography
+)
+
+NO_LINT()
+
+NO_CHECK_IMPORTS(
+ jwt.contrib.*
+)
+
+PY_SRCS(
+ TOP_LEVEL
+ jwt/__init__.py
+ jwt/__main__.py
+ jwt/algorithms.py
+ jwt/api_jws.py
+ jwt/api_jwt.py
+ jwt/compat.py
+ jwt/contrib/__init__.py
+ jwt/contrib/algorithms/__init__.py
+ jwt/contrib/algorithms/py_ecdsa.py
+ jwt/contrib/algorithms/pycrypto.py
+ jwt/exceptions.py
+ jwt/help.py
+ jwt/utils.py
+)
+
+RESOURCE_FILES(
+ PREFIX contrib/python/PyJWT/py2/
+ .dist-info/METADATA
+ .dist-info/entry_points.txt
+ .dist-info/top_level.txt
+)
+
+END()
diff --git a/contrib/python/cffi/py2/.dist-info/METADATA b/contrib/python/cffi/py2/.dist-info/METADATA
index 538e679147a..abe2ac79bb3 100644
--- a/contrib/python/cffi/py2/.dist-info/METADATA
+++ b/contrib/python/cffi/py2/.dist-info/METADATA
@@ -6,6 +6,7 @@ Home-page: http://cffi.readthedocs.org
Author: Armin Rigo, Maciej Fijalkowski
Author-email: [email protected]
License: MIT
+Platform: UNKNOWN
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.7
@@ -18,10 +19,8 @@ Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: License :: OSI Approved :: MIT License
-License-File: LICENSE
Requires-Dist: pycparser
-
CFFI
====
@@ -32,3 +31,5 @@ Contact
-------
`Mailing list <https://groups.google.com/forum/#!forum/python-cffi>`_
+
+
diff --git a/contrib/python/cffi/py2/.dist-info/entry_points.txt b/contrib/python/cffi/py2/.dist-info/entry_points.txt
index 4b0274f2333..eee7e0fb1f4 100644
--- a/contrib/python/cffi/py2/.dist-info/entry_points.txt
+++ b/contrib/python/cffi/py2/.dist-info/entry_points.txt
@@ -1,2 +1,3 @@
[distutils.setup_keywords]
cffi_modules = cffi.setuptools_ext:cffi_modules
+
diff --git a/contrib/python/future/py2/future/backports/http/cookies.py b/contrib/python/future/py2/future/backports/http/cookies.py
index 934fa56a41f..8bb61e22c4b 100644
--- a/contrib/python/future/py2/future/backports/http/cookies.py
+++ b/contrib/python/future/py2/future/backports/http/cookies.py
@@ -461,7 +461,7 @@ _CookiePattern = re.compile(r"""
)? # End of optional value group
\s* # Any number of spaces.
(\s+|;|$) # Ending either at space, semicolon, or EOS.
- """, re.ASCII | re.VERBOSE) # May be removed if safe.
+ """, re.ASCII) # May be removed if safe.
# At long last, here is the cookie class. Using this class is almost just like
diff --git a/contrib/python/future/py2/future/backports/xmlrpc/client.py b/contrib/python/future/py2/future/backports/xmlrpc/client.py
index 3f0cae9b00f..b0b8f5e19ef 100644
--- a/contrib/python/future/py2/future/backports/xmlrpc/client.py
+++ b/contrib/python/future/py2/future/backports/xmlrpc/client.py
@@ -133,12 +133,11 @@ from __future__ import (absolute_import, division, print_function,
unicode_literals)
from future.builtins import bytes, dict, int, range, str
-import sys
import base64
-if sys.version_info[0] < 3:
- # Py2.7 compatibility hack
- base64.encodebytes = base64.encodestring
- base64.decodebytes = base64.decodestring
+# Py2.7 compatibility hack
+base64.encodebytes = base64.encodestring
+base64.decodebytes = base64.decodestring
+import sys
import time
from datetime import datetime
from future.backports.http import client as http_client
diff --git a/contrib/python/future/py2/future/moves/_dummy_thread.py b/contrib/python/future/py2/future/moves/_dummy_thread.py
index e5dca348fbd..688d249bbe7 100644
--- a/contrib/python/future/py2/future/moves/_dummy_thread.py
+++ b/contrib/python/future/py2/future/moves/_dummy_thread.py
@@ -2,10 +2,7 @@ from __future__ import absolute_import
from future.utils import PY3
if PY3:
- try:
- from _dummy_thread import *
- except ImportError:
- from _thread import *
+ from _dummy_thread import *
else:
__future_module__ = True
from dummy_thread import *
diff --git a/contrib/python/future/py2/future/standard_library/__init__.py b/contrib/python/future/py2/future/standard_library/__init__.py
index 41c4f36df25..cff02f95943 100644
--- a/contrib/python/future/py2/future/standard_library/__init__.py
+++ b/contrib/python/future/py2/future/standard_library/__init__.py
@@ -125,7 +125,7 @@ RENAMES = {
# 'Tkinter': 'tkinter',
'_winreg': 'winreg',
'thread': '_thread',
- 'dummy_thread': '_dummy_thread' if sys.version_info < (3, 9) else '_thread',
+ 'dummy_thread': '_dummy_thread',
# 'anydbm': 'dbm', # causes infinite import loop
# 'whichdb': 'dbm', # causes infinite import loop
# anydbm and whichdb are handled by fix_imports2
diff --git a/contrib/python/google-auth/py3/.dist-info/METADATA b/contrib/python/google-auth/py3/.dist-info/METADATA
index b4012b87e36..e0785656a45 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.28.2
+Version: 2.29.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/aws.py b/contrib/python/google-auth/py3/google/auth/aws.py
index 6e0e4e864f1..28c065d3c75 100644
--- a/contrib/python/google-auth/py3/google/auth/aws.py
+++ b/contrib/python/google-auth/py3/google/auth/aws.py
@@ -21,10 +21,11 @@ of long-live service account private keys.
AWS Credentials are initialized using external_account arguments which are
typically loaded from the external credentials JSON file.
-Unlike other Credentials that can be initialized with a list of explicit
-arguments, secrets or credentials, external account clients use the
-environment and hints/guidelines provided by the external_account JSON
-file to retrieve credentials and exchange them for Google access tokens.
+
+This module also provides a definition for an abstract AWS security credentials supplier.
+This supplier can be implemented to return valid AWS security credentials and an AWS region
+and used to create AWS credentials. The credentials will then call the
+supplier instead of using pre-defined methods such as calling the EC2 metadata endpoints.
This module also provides a basic implementation of the
`AWS Signature Version 4`_ request signing algorithm.
@@ -37,6 +38,8 @@ via the GCP STS endpoint.
.. _AWS STS GetCallerIdentity: https://docs.aws.amazon.com/STS/latest/APIReference/API_GetCallerIdentity.html
"""
+import abc
+from dataclasses import dataclass
import hashlib
import hmac
import http.client as http_client
@@ -44,6 +47,7 @@ import json
import os
import posixpath
import re
+from typing import Optional
import urllib
from urllib.parse import urljoin
@@ -61,6 +65,12 @@ _AWS_REQUEST_TYPE = "aws4_request"
_AWS_SECURITY_TOKEN_HEADER = "x-amz-security-token"
# The AWS authorization header name for the auto-generated date.
_AWS_DATE_HEADER = "x-amz-date"
+# The default AWS regional credential verification URL.
+_DEFAULT_AWS_REGIONAL_CREDENTIAL_VERIFICATION_URL = (
+ "https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15"
+)
+# IMDSV2 session token lifetime. This is set to a low value because the session token is used immediately.
+_IMDSV2_SESSION_TOKEN_TTL_SECONDS = "300"
class RequestSigner(object):
@@ -92,8 +102,7 @@ class RequestSigner(object):
https://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html
Args:
- aws_security_credentials (Mapping[str, str]): A dictionary containing
- the AWS security credentials.
+ aws_security_credentials (AWSSecurityCredentials): The AWS security credentials.
url (str): The AWS service URL containing the canonical URI and
query string.
method (str): The HTTP method used to call this API.
@@ -105,10 +114,6 @@ class RequestSigner(object):
Returns:
Mapping[str, str]: The AWS signed request dictionary object.
"""
- # Get AWS credentials.
- access_key = aws_security_credentials.get("access_key_id")
- secret_key = aws_security_credentials.get("secret_access_key")
- security_token = aws_security_credentials.get("security_token")
additional_headers = additional_headers or {}
@@ -129,9 +134,7 @@ class RequestSigner(object):
canonical_querystring=_get_canonical_querystring(uri.query),
method=method,
region=self._region_name,
- access_key=access_key,
- secret_key=secret_key,
- security_token=security_token,
+ aws_security_credentials=aws_security_credentials,
request_payload=request_payload,
additional_headers=additional_headers,
)
@@ -147,8 +150,8 @@ class RequestSigner(object):
headers[key] = additional_headers[key]
# Add session token if available.
- if security_token is not None:
- headers[_AWS_SECURITY_TOKEN_HEADER] = security_token
+ if aws_security_credentials.session_token is not None:
+ headers[_AWS_SECURITY_TOKEN_HEADER] = aws_security_credentials.session_token
signed_request = {"url": url, "method": method, "headers": headers}
if request_payload:
@@ -233,9 +236,7 @@ def _generate_authentication_header_map(
canonical_querystring,
method,
region,
- access_key,
- secret_key,
- security_token,
+ aws_security_credentials,
request_payload="",
additional_headers={},
):
@@ -248,10 +249,7 @@ def _generate_authentication_header_map(
canonical_querystring (str): The AWS service URL query string.
method (str): The HTTP method used to call this API.
region (str): The AWS region.
- access_key (str): The AWS access key ID.
- secret_key (str): The AWS secret access key.
- security_token (Optional[str]): The AWS security session token. This is
- available for temporary sessions.
+ aws_security_credentials (AWSSecurityCredentials): The AWS security credentials.
request_payload (Optional[str]): The optional request payload if
available.
additional_headers (Optional[Mapping[str, str]]): The optional
@@ -274,8 +272,10 @@ def _generate_authentication_header_map(
for key in additional_headers:
full_headers[key.lower()] = additional_headers[key]
# Add AWS session token if available.
- if security_token is not None:
- full_headers[_AWS_SECURITY_TOKEN_HEADER] = security_token
+ if aws_security_credentials.session_token is not None:
+ full_headers[
+ _AWS_SECURITY_TOKEN_HEADER
+ ] = aws_security_credentials.session_token
# Required headers
full_headers["host"] = host
@@ -321,14 +321,20 @@ def _generate_authentication_header_map(
)
# https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html
- signing_key = _get_signing_key(secret_key, date_stamp, region, service_name)
+ signing_key = _get_signing_key(
+ aws_security_credentials.secret_access_key, date_stamp, region, service_name
+ )
signature = hmac.new(
signing_key, string_to_sign.encode("utf-8"), hashlib.sha256
).hexdigest()
# https://docs.aws.amazon.com/general/latest/gr/sigv4-add-signature-to-request.html
authorization_header = "{} Credential={}/{}, SignedHeaders={}, Signature={}".format(
- _AWS_ALGORITHM, access_key, credential_scope, signed_headers, signature
+ _AWS_ALGORITHM,
+ aws_security_credentials.access_key_id,
+ credential_scope,
+ signed_headers,
+ signature,
)
authentication_header = {"authorization_header": authorization_header}
@@ -338,211 +344,112 @@ def _generate_authentication_header_map(
return authentication_header
-class Credentials(external_account.Credentials):
- """AWS external account credentials.
- This is used to exchange serialized AWS signature v4 signed requests to
- AWS STS GetCallerIdentity service for Google access tokens.
- """
+@dataclass
+class AwsSecurityCredentials:
+ """A class that models AWS security credentials with an optional session token.
- def __init__(
- self,
- audience,
- subject_token_type,
- token_url,
- credential_source=None,
- *args,
- **kwargs
- ):
- """Instantiates an AWS workload external account credentials object.
-
- Args:
- audience (str): The STS audience field.
- subject_token_type (str): The subject token type.
- token_url (str): The STS endpoint URL.
- credential_source (Mapping): The credential source dictionary used
- to provide instructions on how to retrieve external credential
- to be exchanged for Google access tokens.
- args (List): Optional positional arguments passed into the underlying :meth:`~external_account.Credentials.__init__` method.
- kwargs (Mapping): Optional keyword arguments passed into the underlying :meth:`~external_account.Credentials.__init__` method.
-
- Raises:
- google.auth.exceptions.RefreshError: If an error is encountered during
- access token retrieval logic.
- ValueError: For invalid parameters.
-
- .. note:: Typically one of the helper constructors
- :meth:`from_file` or
- :meth:`from_info` are used instead of calling the constructor directly.
- """
- super(Credentials, self).__init__(
- audience=audience,
- subject_token_type=subject_token_type,
- token_url=token_url,
- credential_source=credential_source,
- *args,
- **kwargs
- )
- credential_source = credential_source or {}
- self._environment_id = credential_source.get("environment_id") or ""
- self._region_url = credential_source.get("region_url")
- self._security_credentials_url = credential_source.get("url")
- self._cred_verification_url = credential_source.get(
- "regional_cred_verification_url"
- )
- self._imdsv2_session_token_url = credential_source.get(
- "imdsv2_session_token_url"
- )
- self._region = None
- self._request_signer = None
- self._target_resource = audience
-
- # Get the environment ID. Currently, only one version supported (v1).
- matches = re.match(r"^(aws)([\d]+)$", self._environment_id)
- if matches:
- env_id, env_version = matches.groups()
- else:
- env_id, env_version = (None, None)
+ Attributes:
+ access_key_id (str): The AWS security credentials access key id.
+ secret_access_key (str): The AWS security credentials secret access key.
+ session_token (Optional[str]): The optional AWS security credentials session token. This should be set when using temporary credentials.
+ """
- if env_id != "aws" or self._cred_verification_url is None:
- raise exceptions.InvalidResource(
- "No valid AWS 'credential_source' provided"
- )
- elif int(env_version or "") != 1:
- raise exceptions.InvalidValue(
- "aws version '{}' is not supported in the current build.".format(
- env_version
- )
- )
+ access_key_id: str
+ secret_access_key: str
+ session_token: Optional[str] = None
- def retrieve_subject_token(self, request):
- """Retrieves the subject token using the credential_source object.
- The subject token is a serialized `AWS GetCallerIdentity signed request`_.
- The logic is summarized as:
+class AwsSecurityCredentialsSupplier(metaclass=abc.ABCMeta):
+ """Base class for AWS security credential suppliers. This can be implemented with custom logic to retrieve
+ AWS security credentials to exchange for a Google Cloud access token. The AWS external account credential does
+ not cache the AWS security credentials, so caching logic should be added in the implementation.
+ """
- Retrieve the AWS region from the AWS_REGION or AWS_DEFAULT_REGION
- environment variable or from the AWS metadata server availability-zone
- if not found in the environment variable.
+ @abc.abstractmethod
+ def get_aws_security_credentials(self, context, request):
+ """Returns the AWS security credentials for the requested context.
- Check AWS credentials in environment variables. If not found, retrieve
- from the AWS metadata server security-credentials endpoint.
+ .. warning: This is not cached by the calling Google credential, so caching logic should be implemented in the supplier.
- When retrieving AWS credentials from the metadata server
- security-credentials endpoint, the AWS role needs to be determined by
- calling the security-credentials endpoint without any argument. Then the
- credentials can be retrieved via: security-credentials/role_name
+ Args:
+ context (google.auth.externalaccount.SupplierContext): The context object
+ containing information about the requested audience and subject token type.
+ request (google.auth.transport.Request): The object used to make
+ HTTP requests.
- Generate the signed request to AWS STS GetCallerIdentity action.
+ Raises:
+ google.auth.exceptions.RefreshError: If an error is encountered during
+ security credential retrieval logic.
- Inject x-goog-cloud-target-resource into header and serialize the
- signed request. This will be the subject-token to pass to GCP STS.
+ Returns:
+ AwsSecurityCredentials: The requested AWS security credentials.
+ """
+ raise NotImplementedError("")
- .. _AWS GetCallerIdentity signed request:
- https://cloud.google.com/iam/docs/access-resources-aws#exchange-token
+ @abc.abstractmethod
+ def get_aws_region(self, context, request):
+ """Returns the AWS region for the requested context.
Args:
- request (google.auth.transport.Request): A callable used to make
+ context (google.auth.externalaccount.SupplierContext): The context object
+ containing information about the requested audience and subject token type.
+ request (google.auth.transport.Request): The object used to make
HTTP requests.
- Returns:
- str: The retrieved subject token.
- """
- # Fetch the session token required to make meta data endpoint calls to aws.
- if (
- request is not None
- and self._imdsv2_session_token_url is not None
- and self._should_use_metadata_server()
- ):
- headers = {"X-aws-ec2-metadata-token-ttl-seconds": "300"}
- imdsv2_session_token_response = request(
- url=self._imdsv2_session_token_url, method="PUT", headers=headers
- )
+ Raises:
+ google.auth.exceptions.RefreshError: If an error is encountered during
+ region retrieval logic.
- if imdsv2_session_token_response.status != 200:
- raise exceptions.RefreshError(
- "Unable to retrieve AWS Session Token",
- imdsv2_session_token_response.data,
- )
+ Returns:
+ str: The AWS region.
+ """
+ raise NotImplementedError("")
- imdsv2_session_token = imdsv2_session_token_response.data
- else:
- imdsv2_session_token = None
- # Initialize the request signer if not yet initialized after determining
- # the current AWS region.
- if self._request_signer is None:
- self._region = self._get_region(
- request, self._region_url, imdsv2_session_token
- )
- self._request_signer = RequestSigner(self._region)
+class _DefaultAwsSecurityCredentialsSupplier(AwsSecurityCredentialsSupplier):
+ """Default implementation of AWS security credentials supplier. Supports retrieving
+ credentials and region via EC2 metadata endpoints and environment variables.
+ """
- # Retrieve the AWS security credentials needed to generate the signed
- # request.
- aws_security_credentials = self._get_security_credentials(
- request, imdsv2_session_token
- )
- # Generate the signed request to AWS STS GetCallerIdentity API.
- # Use the required regional endpoint. Otherwise, the request will fail.
- request_options = self._request_signer.get_request_options(
- aws_security_credentials,
- self._cred_verification_url.replace("{region}", self._region),
- "POST",
+ def __init__(self, credential_source):
+ self._region_url = credential_source.get("region_url")
+ self._security_credentials_url = credential_source.get("url")
+ self._imdsv2_session_token_url = credential_source.get(
+ "imdsv2_session_token_url"
)
- # The GCP STS endpoint expects the headers to be formatted as:
- # [
- # {key: 'x-amz-date', value: '...'},
- # {key: 'Authorization', value: '...'},
- # ...
- # ]
- # And then serialized as:
- # quote(json.dumps({
- # url: '...',
- # method: 'POST',
- # headers: [{key: 'x-amz-date', value: '...'}, ...]
- # }))
- request_headers = request_options.get("headers")
- # The full, canonical resource name of the workload identity pool
- # provider, with or without the HTTPS prefix.
- # Including this header as part of the signature is recommended to
- # ensure data integrity.
- request_headers["x-goog-cloud-target-resource"] = self._target_resource
- # Serialize AWS signed request.
- # Keeping inner keys in sorted order makes testing easier for Python
- # versions <=3.5 as the stringified JSON string would have a predictable
- # key order.
- aws_signed_req = {}
- aws_signed_req["url"] = request_options.get("url")
- aws_signed_req["method"] = request_options.get("method")
- aws_signed_req["headers"] = []
- # Reformat header to GCP STS expected format.
- for key in sorted(request_headers.keys()):
- aws_signed_req["headers"].append(
- {"key": key, "value": request_headers[key]}
- )
+ @_helpers.copy_docstring(AwsSecurityCredentialsSupplier)
+ def get_aws_security_credentials(self, context, request):
- return urllib.parse.quote(
- json.dumps(aws_signed_req, separators=(",", ":"), sort_keys=True)
+ # Check environment variables for permanent credentials first.
+ # https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html
+ env_aws_access_key_id = os.environ.get(environment_vars.AWS_ACCESS_KEY_ID)
+ env_aws_secret_access_key = os.environ.get(
+ environment_vars.AWS_SECRET_ACCESS_KEY
)
+ # This is normally not available for permanent credentials.
+ env_aws_session_token = os.environ.get(environment_vars.AWS_SESSION_TOKEN)
+ if env_aws_access_key_id and env_aws_secret_access_key:
+ return AwsSecurityCredentials(
+ env_aws_access_key_id, env_aws_secret_access_key, env_aws_session_token
+ )
- def _get_region(self, request, url, imdsv2_session_token):
- """Retrieves the current AWS region from either the AWS_REGION or
- AWS_DEFAULT_REGION environment variable or from the AWS metadata server.
+ imdsv2_session_token = self._get_imdsv2_session_token(request)
+ role_name = self._get_metadata_role_name(request, imdsv2_session_token)
- Args:
- request (google.auth.transport.Request): A callable used to make
- HTTP requests.
- url (str): The AWS metadata server region URL.
- imdsv2_session_token (str): The AWS IMDSv2 session token to be added as a
- header in the requests to AWS metadata endpoint.
+ # Get security credentials.
+ credentials = self._get_metadata_security_credentials(
+ request, role_name, imdsv2_session_token
+ )
- Returns:
- str: The current AWS region.
+ return AwsSecurityCredentials(
+ credentials.get("AccessKeyId"),
+ credentials.get("SecretAccessKey"),
+ credentials.get("Token"),
+ )
- Raises:
- google.auth.exceptions.RefreshError: If an error occurs while
- retrieving the AWS region.
- """
+ @_helpers.copy_docstring(AwsSecurityCredentialsSupplier)
+ def get_aws_region(self, context, request):
# The AWS metadata server is not available in some AWS environments
# such as AWS lambda. Instead, it is available via environment
# variable.
@@ -558,6 +465,7 @@ class Credentials(external_account.Credentials):
raise exceptions.RefreshError("Unable to determine AWS region")
headers = None
+ imdsv2_session_token = self._get_imdsv2_session_token(request)
if imdsv2_session_token is not None:
headers = {"X-aws-ec2-metadata-token": imdsv2_session_token}
@@ -570,62 +478,35 @@ class Credentials(external_account.Credentials):
else response.data
)
- if response.status != 200:
+ if response.status != http_client.OK:
raise exceptions.RefreshError(
- "Unable to retrieve AWS region", response_body
+ "Unable to retrieve AWS region: {}".format(response_body)
)
# This endpoint will return the region in format: us-east-2b.
# Only the us-east-2 part should be used.
return response_body[:-1]
- def _get_security_credentials(self, request, imdsv2_session_token):
- """Retrieves the AWS security credentials required for signing AWS
- requests from either the AWS security credentials environment variables
- or from the AWS metadata server.
-
- Args:
- request (google.auth.transport.Request): A callable used to make
- HTTP requests.
- imdsv2_session_token (str): The AWS IMDSv2 session token to be added as a
- header in the requests to AWS metadata endpoint.
-
- Returns:
- Mapping[str, str]: The AWS security credentials dictionary object.
-
- Raises:
- google.auth.exceptions.RefreshError: If an error occurs while
- retrieving the AWS security credentials.
- """
-
- # Check environment variables for permanent credentials first.
- # https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html
- env_aws_access_key_id = os.environ.get(environment_vars.AWS_ACCESS_KEY_ID)
- env_aws_secret_access_key = os.environ.get(
- environment_vars.AWS_SECRET_ACCESS_KEY
- )
- # This is normally not available for permanent credentials.
- env_aws_session_token = os.environ.get(environment_vars.AWS_SESSION_TOKEN)
- if env_aws_access_key_id and env_aws_secret_access_key:
- return {
- "access_key_id": env_aws_access_key_id,
- "secret_access_key": env_aws_secret_access_key,
- "security_token": env_aws_session_token,
+ def _get_imdsv2_session_token(self, request):
+ if request is not None and self._imdsv2_session_token_url is not None:
+ headers = {
+ "X-aws-ec2-metadata-token-ttl-seconds": _IMDSV2_SESSION_TOKEN_TTL_SECONDS
}
- # Get role name.
- role_name = self._get_metadata_role_name(request, imdsv2_session_token)
+ imdsv2_session_token_response = request(
+ url=self._imdsv2_session_token_url, method="PUT", headers=headers
+ )
- # Get security credentials.
- credentials = self._get_metadata_security_credentials(
- request, role_name, imdsv2_session_token
- )
+ if imdsv2_session_token_response.status != http_client.OK:
+ raise exceptions.RefreshError(
+ "Unable to retrieve AWS Session Token: {}".format(
+ imdsv2_session_token_response.data
+ )
+ )
- return {
- "access_key_id": credentials.get("AccessKeyId"),
- "secret_access_key": credentials.get("SecretAccessKey"),
- "security_token": credentials.get("Token"),
- }
+ return imdsv2_session_token_response.data
+ else:
+ return None
def _get_metadata_security_credentials(
self, request, role_name, imdsv2_session_token
@@ -669,7 +550,7 @@ class Credentials(external_account.Credentials):
if response.status != http_client.OK:
raise exceptions.RefreshError(
- "Unable to retrieve AWS security credentials", response_body
+ "Unable to retrieve AWS security credentials: {}".format(response_body)
)
credentials_response = json.loads(response_body)
@@ -717,35 +598,232 @@ class Credentials(external_account.Credentials):
if response.status != http_client.OK:
raise exceptions.RefreshError(
- "Unable to retrieve AWS role name", response_body
+ "Unable to retrieve AWS role name {}".format(response_body)
)
return response_body
- def _should_use_metadata_server(self):
- # The AWS region can be provided through AWS_REGION or AWS_DEFAULT_REGION.
- # The metadata server should be used if it cannot be retrieved from one of
- # these environment variables.
- if not os.environ.get(environment_vars.AWS_REGION) and not os.environ.get(
- environment_vars.AWS_DEFAULT_REGION
- ):
- return True
- # AWS security credentials can be retrieved from the AWS_ACCESS_KEY_ID
- # and AWS_SECRET_ACCESS_KEY environment variables. The metadata server
- # should be used if either of these are not available.
- if not os.environ.get(environment_vars.AWS_ACCESS_KEY_ID) or not os.environ.get(
- environment_vars.AWS_SECRET_ACCESS_KEY
+class Credentials(external_account.Credentials):
+ """AWS external account credentials.
+ This is used to exchange serialized AWS signature v4 signed requests to
+ AWS STS GetCallerIdentity service for Google access tokens.
+ """
+
+ def __init__(
+ self,
+ audience,
+ subject_token_type,
+ token_url=external_account._DEFAULT_TOKEN_URL,
+ credential_source=None,
+ aws_security_credentials_supplier=None,
+ *args,
+ **kwargs
+ ):
+ """Instantiates an AWS workload external account credentials object.
+
+ Args:
+ audience (str): The STS audience field.
+ subject_token_type (str): The subject token type based on the Oauth2.0 token exchange spec.
+ Expected values include::
+
+ “urn:ietf:params:aws:token-type:aws4_request”
+
+ token_url (Optional [str]): The STS endpoint URL. If not provided, will default to "https://sts.googleapis.com/v1/token".
+ credential_source (Optional [Mapping]): The credential source dictionary used
+ to provide instructions on how to retrieve external credential to be exchanged for Google access tokens.
+ Either a credential source or an AWS security credentials supplier must be provided.
+
+ Example credential_source for AWS credential::
+
+ {
+ "environment_id": "aws1",
+ "regional_cred_verification_url": "https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15",
+ "region_url": "http://169.254.169.254/latest/meta-data/placement/availability-zone",
+ "url": "http://169.254.169.254/latest/meta-data/iam/security-credentials",
+ imdsv2_session_token_url": "http://169.254.169.254/latest/api/token"
+ }
+
+ aws_security_credentials_supplier (Optional [AwsSecurityCredentialsSupplier]): Optional AWS security credentials supplier.
+ This will be called to supply valid AWS security credentails which will then
+ be exchanged for Google access tokens. Either an AWS security credentials supplier
+ or a credential source must be provided.
+ args (List): Optional positional arguments passed into the underlying :meth:`~external_account.Credentials.__init__` method.
+ kwargs (Mapping): Optional keyword arguments passed into the underlying :meth:`~external_account.Credentials.__init__` method.
+
+ Raises:
+ google.auth.exceptions.RefreshError: If an error is encountered during
+ access token retrieval logic.
+ ValueError: For invalid parameters.
+
+ .. note:: Typically one of the helper constructors
+ :meth:`from_file` or
+ :meth:`from_info` are used instead of calling the constructor directly.
+ """
+ super(Credentials, self).__init__(
+ audience=audience,
+ subject_token_type=subject_token_type,
+ token_url=token_url,
+ credential_source=credential_source,
+ *args,
+ **kwargs
+ )
+ if credential_source is None and aws_security_credentials_supplier is None:
+ raise exceptions.InvalidValue(
+ "A valid credential source or AWS security credentials supplier must be provided."
+ )
+ if (
+ credential_source is not None
+ and aws_security_credentials_supplier is not None
):
- return True
+ raise exceptions.InvalidValue(
+ "AWS credential cannot have both a credential source and an AWS security credentials supplier."
+ )
+
+ if aws_security_credentials_supplier:
+ self._aws_security_credentials_supplier = aws_security_credentials_supplier
+ # The regional cred verification URL would normally be provided through the credential source. So set it to the default one here.
+ self._cred_verification_url = (
+ _DEFAULT_AWS_REGIONAL_CREDENTIAL_VERIFICATION_URL
+ )
+ else:
+ environment_id = credential_source.get("environment_id") or ""
+ self._aws_security_credentials_supplier = _DefaultAwsSecurityCredentialsSupplier(
+ credential_source
+ )
+ self._cred_verification_url = credential_source.get(
+ "regional_cred_verification_url"
+ )
- return False
+ # Get the environment ID, i.e. "aws1". Currently, only one version supported (1).
+ matches = re.match(r"^(aws)([\d]+)$", environment_id)
+ if matches:
+ env_id, env_version = matches.groups()
+ else:
+ env_id, env_version = (None, None)
+
+ if env_id != "aws" or self._cred_verification_url is None:
+ raise exceptions.InvalidResource(
+ "No valid AWS 'credential_source' provided"
+ )
+ elif env_version is None or int(env_version) != 1:
+ raise exceptions.InvalidValue(
+ "aws version '{}' is not supported in the current build.".format(
+ env_version
+ )
+ )
+
+ self._target_resource = audience
+ self._request_signer = None
+
+ def retrieve_subject_token(self, request):
+ """Retrieves the subject token using the credential_source object.
+ The subject token is a serialized `AWS GetCallerIdentity signed request`_.
+
+ The logic is summarized as:
+
+ Retrieve the AWS region from the AWS_REGION or AWS_DEFAULT_REGION
+ environment variable or from the AWS metadata server availability-zone
+ if not found in the environment variable.
+
+ Check AWS credentials in environment variables. If not found, retrieve
+ from the AWS metadata server security-credentials endpoint.
+
+ When retrieving AWS credentials from the metadata server
+ security-credentials endpoint, the AWS role needs to be determined by
+ calling the security-credentials endpoint without any argument. Then the
+ credentials can be retrieved via: security-credentials/role_name
+
+ Generate the signed request to AWS STS GetCallerIdentity action.
+
+ Inject x-goog-cloud-target-resource into header and serialize the
+ signed request. This will be the subject-token to pass to GCP STS.
+
+ .. _AWS GetCallerIdentity signed request:
+ https://cloud.google.com/iam/docs/access-resources-aws#exchange-token
+
+ Args:
+ request (google.auth.transport.Request): A callable used to make
+ HTTP requests.
+ Returns:
+ str: The retrieved subject token.
+ """
+
+ # Initialize the request signer if not yet initialized after determining
+ # the current AWS region.
+ if self._request_signer is None:
+ self._region = self._aws_security_credentials_supplier.get_aws_region(
+ self._supplier_context, request
+ )
+ self._request_signer = RequestSigner(self._region)
+
+ # Retrieve the AWS security credentials needed to generate the signed
+ # request.
+ aws_security_credentials = self._aws_security_credentials_supplier.get_aws_security_credentials(
+ self._supplier_context, request
+ )
+ # Generate the signed request to AWS STS GetCallerIdentity API.
+ # Use the required regional endpoint. Otherwise, the request will fail.
+ request_options = self._request_signer.get_request_options(
+ aws_security_credentials,
+ self._cred_verification_url.replace("{region}", self._region),
+ "POST",
+ )
+ # The GCP STS endpoint expects the headers to be formatted as:
+ # [
+ # {key: 'x-amz-date', value: '...'},
+ # {key: 'Authorization', value: '...'},
+ # ...
+ # ]
+ # And then serialized as:
+ # quote(json.dumps({
+ # url: '...',
+ # method: 'POST',
+ # headers: [{key: 'x-amz-date', value: '...'}, ...]
+ # }))
+ request_headers = request_options.get("headers")
+ # The full, canonical resource name of the workload identity pool
+ # provider, with or without the HTTPS prefix.
+ # Including this header as part of the signature is recommended to
+ # ensure data integrity.
+ request_headers["x-goog-cloud-target-resource"] = self._target_resource
+
+ # Serialize AWS signed request.
+ aws_signed_req = {}
+ aws_signed_req["url"] = request_options.get("url")
+ aws_signed_req["method"] = request_options.get("method")
+ aws_signed_req["headers"] = []
+ # Reformat header to GCP STS expected format.
+ for key in request_headers.keys():
+ aws_signed_req["headers"].append(
+ {"key": key, "value": request_headers[key]}
+ )
+
+ return urllib.parse.quote(
+ json.dumps(aws_signed_req, separators=(",", ":"), sort_keys=True)
+ )
def _create_default_metrics_options(self):
metrics_options = super(Credentials, self)._create_default_metrics_options()
metrics_options["source"] = "aws"
+ if self._has_custom_supplier():
+ metrics_options["source"] = "programmatic"
return metrics_options
+ def _has_custom_supplier(self):
+ return self._credential_source is None
+
+ def _constructor_args(self):
+ args = super(Credentials, self)._constructor_args()
+ # If a custom supplier was used, append it to the args dict.
+ if self._has_custom_supplier():
+ args.update(
+ {
+ "aws_security_credentials_supplier": self._aws_security_credentials_supplier
+ }
+ )
+ return args
+
@classmethod
def from_info(cls, info, **kwargs):
"""Creates an AWS Credentials instance from parsed external account info.
@@ -761,6 +839,12 @@ class Credentials(external_account.Credentials):
Raises:
ValueError: For invalid parameters.
"""
+ aws_security_credentials_supplier = info.get(
+ "aws_security_credentials_supplier"
+ )
+ kwargs.update(
+ {"aws_security_credentials_supplier": aws_security_credentials_supplier}
+ )
return super(Credentials, cls).from_info(info, **kwargs)
@classmethod
diff --git a/contrib/python/google-auth/py3/google/auth/external_account.py b/contrib/python/google-auth/py3/google/auth/external_account.py
index 0420883f86b..c14001bc2b1 100644
--- a/contrib/python/google-auth/py3/google/auth/external_account.py
+++ b/contrib/python/google-auth/py3/google/auth/external_account.py
@@ -29,6 +29,7 @@ token exchange endpoint following the `OAuth 2.0 Token Exchange`_ spec.
import abc
import copy
+from dataclasses import dataclass
import datetime
import io
import json
@@ -50,6 +51,29 @@ _STS_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:token-exchange"
_STS_REQUESTED_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token"
# Cloud resource manager URL used to retrieve project information.
_CLOUD_RESOURCE_MANAGER = "https://cloudresourcemanager.googleapis.com/v1/projects/"
+# Default Google sts token url.
+_DEFAULT_TOKEN_URL = "https://sts.googleapis.com/v1/token"
+
+
+@dataclass
+class SupplierContext:
+ """A context class that contains information about the requested third party credential that is passed
+ to AWS security credential and subject token suppliers.
+
+ Attributes:
+ subject_token_type (str): The requested subject token type based on the Oauth2.0 token exchange spec.
+ Expected values include::
+
+ “urn:ietf:params:oauth:token-type:jwt”
+ “urn:ietf:params:oauth:token-type:id-token”
+ “urn:ietf:params:oauth:token-type:saml2”
+ “urn:ietf:params:aws:token-type:aws4_request”
+
+ audience (str): The requested audience for the subject token.
+ """
+
+ subject_token_type: str
+ audience: str
class Credentials(
@@ -88,7 +112,14 @@ class Credentials(
Args:
audience (str): The STS audience field.
- subject_token_type (str): The subject token type.
+ subject_token_type (str): The subject token type based on the Oauth2.0 token exchange spec.
+ Expected values include::
+
+ “urn:ietf:params:oauth:token-type:jwt”
+ “urn:ietf:params:oauth:token-type:id-token”
+ “urn:ietf:params:oauth:token-type:saml2”
+ “urn:ietf:params:aws:token-type:aws4_request”
+
token_url (str): The STS endpoint URL.
credential_source (Mapping): The credential source dictionary.
service_account_impersonation_url (Optional[str]): The optional service account
@@ -145,11 +176,11 @@ class Credentials(
self._metrics_options = self._create_default_metrics_options()
- if self._service_account_impersonation_url:
- self._impersonated_credentials = self._initialize_impersonated_credentials()
- else:
- self._impersonated_credentials = None
+ self._impersonated_credentials = None
self._project_id = None
+ self._supplier_context = SupplierContext(
+ self._subject_token_type, self._audience
+ )
if not self.is_workforce_pool and self._workforce_pool_user_project:
# Workload identity pools do not support workforce pool user projects.
@@ -358,6 +389,10 @@ class Credentials(
@_helpers.copy_docstring(credentials.Credentials)
def refresh(self, request):
scopes = self._scopes if self._scopes is not None else self._default_scopes
+
+ if self._should_initialize_impersonated_credentials():
+ self._impersonated_credentials = self._initialize_impersonated_credentials()
+
if self._impersonated_credentials:
self._impersonated_credentials.refresh(request)
self.token = self._impersonated_credentials.token
@@ -421,6 +456,12 @@ class Credentials(
new_cred._metrics_options = self._metrics_options
return new_cred
+ def _should_initialize_impersonated_credentials(self):
+ return (
+ self._service_account_impersonation_url is not None
+ and self._impersonated_credentials is None
+ )
+
def _initialize_impersonated_credentials(self):
"""Generates an impersonated credentials.
diff --git a/contrib/python/google-auth/py3/google/auth/identity_pool.py b/contrib/python/google-auth/py3/google/auth/identity_pool.py
index a515353c376..a9ec5773346 100644
--- a/contrib/python/google-auth/py3/google/auth/identity_pool.py
+++ b/contrib/python/google-auth/py3/google/auth/identity_pool.py
@@ -26,11 +26,13 @@ long-live service account private keys.
Identity Pool Credentials are initialized using external_account
arguments which are typically loaded from an external credentials file or
-an external credentials URL. Unlike other Credentials that can be initialized
-with a list of explicit arguments, secrets or credentials, external account
-clients use the environment and hints/guidelines provided by the
-external_account JSON file to retrieve credentials and exchange them for Google
-access tokens.
+an external credentials URL.
+
+This module also provides a definition for an abstract subject token supplier.
+This supplier can be implemented to return a valid OIDC or SAML2.0 subject token
+and used to create Identity Pool credentials. The credentials will then call the
+supplier instead of using pre-defined methods such as reading a local file or
+calling a URL.
"""
try:
@@ -38,15 +40,129 @@ try:
# Python 2.7 compatibility
except ImportError: # pragma: NO COVER
from collections import Mapping
-import io
+import abc
import json
import os
+from typing import NamedTuple
from google.auth import _helpers
from google.auth import exceptions
from google.auth import external_account
+class SubjectTokenSupplier(metaclass=abc.ABCMeta):
+ """Base class for subject token suppliers. This can be implemented with custom logic to retrieve
+ a subject token to exchange for a Google Cloud access token when using Workload or
+ Workforce Identity Federation. The identity pool credential does not cache the subject token,
+ so caching logic should be added in the implementation.
+ """
+
+ @abc.abstractmethod
+ def get_subject_token(self, context, request):
+ """Returns the requested subject token. The subject token must be valid.
+
+ .. warning: This is not cached by the calling Google credential, so caching logic should be implemented in the supplier.
+
+ Args:
+ context (google.auth.externalaccount.SupplierContext): The context object
+ containing information about the requested audience and subject token type.
+ request (google.auth.transport.Request): The object used to make
+ HTTP requests.
+
+ Raises:
+ google.auth.exceptions.RefreshError: If an error is encountered during
+ subject token retrieval logic.
+
+ Returns:
+ str: The requested subject token string.
+ """
+ raise NotImplementedError("")
+
+
+class _TokenContent(NamedTuple):
+ """Models the token content response from file and url internal suppliers.
+ Attributes:
+ content (str): The string content of the file or URL response.
+ location (str): The location the content was retrieved from. This will either be a file location or a URL.
+ """
+
+ content: str
+ location: str
+
+
+class _FileSupplier(SubjectTokenSupplier):
+ """ Internal implementation of subject token supplier which supports reading a subject token from a file."""
+
+ def __init__(self, path, format_type, subject_token_field_name):
+ self._path = path
+ self._format_type = format_type
+ self._subject_token_field_name = subject_token_field_name
+
+ @_helpers.copy_docstring(SubjectTokenSupplier)
+ def get_subject_token(self, context, request):
+ if not os.path.exists(self._path):
+ raise exceptions.RefreshError("File '{}' was not found.".format(self._path))
+
+ with open(self._path, "r", encoding="utf-8") as file_obj:
+ token_content = _TokenContent(file_obj.read(), self._path)
+
+ return _parse_token_data(
+ token_content, self._format_type, self._subject_token_field_name
+ )
+
+
+class _UrlSupplier(SubjectTokenSupplier):
+ """ Internal implementation of subject token supplier which supports retrieving a subject token by calling a URL endpoint."""
+
+ def __init__(self, url, format_type, subject_token_field_name, headers):
+ self._url = url
+ self._format_type = format_type
+ self._subject_token_field_name = subject_token_field_name
+ self._headers = headers
+
+ @_helpers.copy_docstring(SubjectTokenSupplier)
+ def get_subject_token(self, context, request):
+ response = request(url=self._url, method="GET", headers=self._headers)
+
+ # support both string and bytes type response.data
+ response_body = (
+ response.data.decode("utf-8")
+ if hasattr(response.data, "decode")
+ else response.data
+ )
+
+ if response.status != 200:
+ raise exceptions.RefreshError(
+ "Unable to retrieve Identity Pool subject token", response_body
+ )
+ token_content = _TokenContent(response_body, self._url)
+ return _parse_token_data(
+ token_content, self._format_type, self._subject_token_field_name
+ )
+
+
+def _parse_token_data(token_content, format_type="text", subject_token_field_name=None):
+ if format_type == "text":
+ token = token_content.content
+ else:
+ try:
+ # Parse file content as JSON.
+ response_data = json.loads(token_content.content)
+ # Get the subject_token.
+ token = response_data[subject_token_field_name]
+ except (KeyError, ValueError):
+ raise exceptions.RefreshError(
+ "Unable to parse subject_token from JSON file '{}' using key '{}'".format(
+ token_content.location, subject_token_field_name
+ )
+ )
+ if not token:
+ raise exceptions.RefreshError(
+ "Missing subject_token in the credential_source file"
+ )
+ return token
+
+
class Credentials(external_account.Credentials):
"""External account credentials sourced from files and URLs."""
@@ -54,8 +170,9 @@ class Credentials(external_account.Credentials):
self,
audience,
subject_token_type,
- token_url,
- credential_source,
+ token_url=external_account._DEFAULT_TOKEN_URL,
+ credential_source=None,
+ subject_token_supplier=None,
*args,
**kwargs
):
@@ -63,11 +180,18 @@ class Credentials(external_account.Credentials):
Args:
audience (str): The STS audience field.
- subject_token_type (str): The subject token type.
- token_url (str): The STS endpoint URL.
- credential_source (Mapping): The credential source dictionary used to
+ subject_token_type (str): The subject token type based on the Oauth2.0 token exchange spec.
+ Expected values include::
+
+ “urn:ietf:params:oauth:token-type:jwt”
+ “urn:ietf:params:oauth:token-type:id-token”
+ “urn:ietf:params:oauth:token-type:saml2”
+
+ token_url (Optional [str]): The STS endpoint URL. If not provided, will default to "https://sts.googleapis.com/v1/token".
+ credential_source (Optional [Mapping]): The credential source dictionary used to
provide instructions on how to retrieve external credential to be
- exchanged for Google access tokens.
+ exchanged for Google access tokens. Either a credential source or
+ a subject token supplier must be provided.
Example credential_source for url-sourced credential::
@@ -85,6 +209,10 @@ class Credentials(external_account.Credentials):
{
"file": "/path/to/token/file.txt"
}
+ subject_token_supplier (Optional [SubjectTokenSupplier]): Optional subject token supplier.
+ This will be called to supply a valid subject token which will then
+ be exchanged for Google access tokens. Either a subject token supplier
+ or a credential source must be provided.
args (List): Optional positional arguments passed into the underlying :meth:`~external_account.Credentials.__init__` method.
kwargs (Mapping): Optional keyword arguments passed into the underlying :meth:`~external_account.Credentials.__init__` method.
@@ -106,10 +234,25 @@ class Credentials(external_account.Credentials):
*args,
**kwargs
)
- if not isinstance(credential_source, Mapping):
+ if credential_source is None and subject_token_supplier is None:
+ raise exceptions.InvalidValue(
+ "A valid credential source or a subject token supplier must be provided."
+ )
+ if credential_source is not None and subject_token_supplier is not None:
+ raise exceptions.InvalidValue(
+ "Identity pool credential cannot have both a credential source and a subject token supplier."
+ )
+
+ if subject_token_supplier is not None:
+ self._subject_token_supplier = subject_token_supplier
self._credential_source_file = None
self._credential_source_url = None
else:
+ if not isinstance(credential_source, Mapping):
+ self._credential_source_executable = None
+ raise exceptions.MalformedError(
+ "Invalid credential_source. The credential_source is not a dict."
+ )
self._credential_source_file = credential_source.get("file")
self._credential_source_url = credential_source.get("url")
self._credential_source_headers = credential_source.get("headers")
@@ -143,79 +286,35 @@ class Credentials(external_account.Credentials):
else:
self._credential_source_field_name = None
- if self._credential_source_file and self._credential_source_url:
- raise exceptions.MalformedError(
- "Ambiguous credential_source. 'file' is mutually exclusive with 'url'."
- )
- if not self._credential_source_file and not self._credential_source_url:
- raise exceptions.MalformedError(
- "Missing credential_source. A 'file' or 'url' must be provided."
- )
+ if self._credential_source_file and self._credential_source_url:
+ raise exceptions.MalformedError(
+ "Ambiguous credential_source. 'file' is mutually exclusive with 'url'."
+ )
+ if not self._credential_source_file and not self._credential_source_url:
+ raise exceptions.MalformedError(
+ "Missing credential_source. A 'file' or 'url' must be provided."
+ )
+
+ if self._credential_source_file:
+ self._subject_token_supplier = _FileSupplier(
+ self._credential_source_file,
+ self._credential_source_format_type,
+ self._credential_source_field_name,
+ )
+ else:
+ self._subject_token_supplier = _UrlSupplier(
+ self._credential_source_url,
+ self._credential_source_format_type,
+ self._credential_source_field_name,
+ self._credential_source_headers,
+ )
@_helpers.copy_docstring(external_account.Credentials)
def retrieve_subject_token(self, request):
- return self._parse_token_data(
- self._get_token_data(request),
- self._credential_source_format_type,
- self._credential_source_field_name,
- )
-
- def _get_token_data(self, request):
- if self._credential_source_file:
- return self._get_file_data(self._credential_source_file)
- else:
- return self._get_url_data(
- request, self._credential_source_url, self._credential_source_headers
- )
-
- def _get_file_data(self, filename):
- if not os.path.exists(filename):
- raise exceptions.RefreshError("File '{}' was not found.".format(filename))
-
- with io.open(filename, "r", encoding="utf-8") as file_obj:
- return file_obj.read(), filename
-
- def _get_url_data(self, request, url, headers):
- response = request(url=url, method="GET", headers=headers)
-
- # support both string and bytes type response.data
- response_body = (
- response.data.decode("utf-8")
- if hasattr(response.data, "decode")
- else response.data
+ return self._subject_token_supplier.get_subject_token(
+ self._supplier_context, request
)
- if response.status != 200:
- raise exceptions.RefreshError(
- "Unable to retrieve Identity Pool subject token", response_body
- )
-
- return response_body, url
-
- def _parse_token_data(
- self, token_content, format_type="text", subject_token_field_name=None
- ):
- content, filename = token_content
- if format_type == "text":
- token = content
- else:
- try:
- # Parse file content as JSON.
- response_data = json.loads(content)
- # Get the subject_token.
- token = response_data[subject_token_field_name]
- except (KeyError, ValueError):
- raise exceptions.RefreshError(
- "Unable to parse subject_token from JSON file '{}' using key '{}'".format(
- filename, subject_token_field_name
- )
- )
- if not token:
- raise exceptions.RefreshError(
- "Missing subject_token in the credential_source file"
- )
- return token
-
def _create_default_metrics_options(self):
metrics_options = super(Credentials, self)._create_default_metrics_options()
# Check that credential source is a dict before checking for file vs url. This check needs to be done
@@ -226,8 +325,20 @@ class Credentials(external_account.Credentials):
metrics_options["source"] = "file"
else:
metrics_options["source"] = "url"
+ else:
+ metrics_options["source"] = "programmatic"
return metrics_options
+ def _has_custom_supplier(self):
+ return self._credential_source is None
+
+ def _constructor_args(self):
+ args = super(Credentials, self)._constructor_args()
+ # If a custom supplier was used, append it to the args dict.
+ if self._has_custom_supplier():
+ args.update({"subject_token_supplier": self._subject_token_supplier})
+ return args
+
@classmethod
def from_info(cls, info, **kwargs):
"""Creates an Identity Pool Credentials instance from parsed external account info.
@@ -244,6 +355,8 @@ class Credentials(external_account.Credentials):
Raises:
ValueError: For invalid parameters.
"""
+ subject_token_supplier = info.get("subject_token_supplier")
+ kwargs.update({"subject_token_supplier": subject_token_supplier})
return super(Credentials, cls).from_info(info, **kwargs)
@classmethod
diff --git a/contrib/python/google-auth/py3/google/auth/version.py b/contrib/python/google-auth/py3/google/auth/version.py
index 0959c754195..f0dd919dca6 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.28.2"
+__version__ = "2.29.0"
diff --git a/contrib/python/google-auth/py3/tests/test_aws.py b/contrib/python/google-auth/py3/tests/test_aws.py
index 3f358d52b09..56148203128 100644
--- a/contrib/python/google-auth/py3/tests/test_aws.py
+++ b/contrib/python/google-auth/py3/tests/test_aws.py
@@ -21,7 +21,7 @@ import urllib.parse
import mock
import pytest # type: ignore
-from google.auth import _helpers
+from google.auth import _helpers, external_account
from google.auth import aws
from google.auth import environment_vars
from google.auth import exceptions
@@ -616,8 +616,13 @@ class TestRequestSigner(object):
):
utcnow.return_value = datetime.datetime.strptime(time, "%Y-%m-%dT%H:%M:%SZ")
request_signer = aws.RequestSigner(region)
+ credentials_object = aws.AwsSecurityCredentials(
+ credentials.get("access_key_id"),
+ credentials.get("secret_access_key"),
+ credentials.get("security_token"),
+ )
actual_signed_request = request_signer.get_request_options(
- credentials,
+ credentials_object,
original_request.get("url"),
original_request.get("method"),
original_request.get("data"),
@@ -631,10 +636,7 @@ class TestRequestSigner(object):
with pytest.raises(ValueError) as excinfo:
request_signer.get_request_options(
- {
- "access_key_id": ACCESS_KEY_ID,
- "secret_access_key": SECRET_ACCESS_KEY,
- },
+ aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY),
"invalid",
"POST",
)
@@ -646,10 +648,7 @@ class TestRequestSigner(object):
with pytest.raises(ValueError) as excinfo:
request_signer.get_request_options(
- {
- "access_key_id": ACCESS_KEY_ID,
- "secret_access_key": SECRET_ACCESS_KEY,
- },
+ aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY),
"http://invalid",
"POST",
)
@@ -661,10 +660,7 @@ class TestRequestSigner(object):
with pytest.raises(ValueError) as excinfo:
request_signer.get_request_options(
- {
- "access_key_id": ACCESS_KEY_ID,
- "secret_access_key": SECRET_ACCESS_KEY,
- },
+ aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY),
"https://",
"POST",
)
@@ -672,6 +668,36 @@ class TestRequestSigner(object):
assert excinfo.match(r"Invalid AWS service URL")
+class TestAwsSecurityCredentialsSupplier(aws.AwsSecurityCredentialsSupplier):
+ def __init__(
+ self,
+ security_credentials=None,
+ region=None,
+ credentials_exception=None,
+ region_exception=None,
+ expected_context=None,
+ ):
+ self._security_credentials = security_credentials
+ self._region = region
+ self._credentials_exception = credentials_exception
+ self._region_exception = region_exception
+ self._expected_context = expected_context
+
+ def get_aws_security_credentials(self, context, request):
+ if self._expected_context is not None:
+ assert self._expected_context == context
+ if self._credentials_exception is not None:
+ raise self._credentials_exception
+ return self._security_credentials
+
+ def get_aws_region(self, context, request):
+ if self._expected_context is not None:
+ assert self._expected_context == context
+ if self._region_exception is not None:
+ raise self._region_exception
+ return self._region
+
+
class TestCredentials(object):
AWS_REGION = "us-east-2"
AWS_ROLE = "gcp-aws-role"
@@ -734,7 +760,7 @@ class TestCredentials(object):
],
}
# Include security token if available.
- if "security_token" in aws_security_credentials:
+ if aws_security_credentials.session_token is not None:
reformatted_signed_request.get("headers").append(
{
"key": "x-amz-security-token",
@@ -773,16 +799,17 @@ class TestCredentials(object):
in an AWS environment.
"""
responses = []
- if imdsv2_session_token_status:
- # AWS session token request
- imdsv2_session_response = mock.create_autospec(
- transport.Response, instance=True
- )
- imdsv2_session_response.status = imdsv2_session_token_status
- imdsv2_session_response.data = imdsv2_session_token_data
- responses.append(imdsv2_session_response)
if region_status:
+ if imdsv2_session_token_status:
+ # AWS session token request
+ imdsv2_session_response = mock.create_autospec(
+ transport.Response, instance=True
+ )
+ imdsv2_session_response.status = imdsv2_session_token_status
+ imdsv2_session_response.data = imdsv2_session_token_data
+ responses.append(imdsv2_session_response)
+
# AWS region request.
region_response = mock.create_autospec(transport.Response, instance=True)
region_response.status = region_status
@@ -790,6 +817,15 @@ class TestCredentials(object):
region_response.data = "{}b".format(region_name).encode("utf-8")
responses.append(region_response)
+ if imdsv2_session_token_status:
+ # AWS session token request
+ imdsv2_session_response = mock.create_autospec(
+ transport.Response, instance=True
+ )
+ imdsv2_session_response.status = imdsv2_session_token_status
+ imdsv2_session_response.data = imdsv2_session_token_data
+ responses.append(imdsv2_session_response)
+
if role_status:
# AWS role name request.
role_response = mock.create_autospec(transport.Response, instance=True)
@@ -834,7 +870,8 @@ class TestCredentials(object):
@classmethod
def make_credentials(
cls,
- credential_source,
+ credential_source=None,
+ aws_security_credentials_supplier=None,
token_url=TOKEN_URL,
token_info_url=TOKEN_INFO_URL,
client_id=None,
@@ -851,6 +888,7 @@ class TestCredentials(object):
token_info_url=token_info_url,
service_account_impersonation_url=service_account_impersonation_url,
credential_source=credential_source,
+ aws_security_credentials_supplier=aws_security_credentials_supplier,
client_id=client_id,
client_secret=client_secret,
quota_project_id=quota_project_id,
@@ -929,6 +967,7 @@ class TestCredentials(object):
client_id=CLIENT_ID,
client_secret=CLIENT_SECRET,
credential_source=self.CREDENTIAL_SOURCE,
+ aws_security_credentials_supplier=None,
quota_project_id=QUOTA_PROJECT_ID,
workforce_pool_user_project=None,
universe_domain=DEFAULT_UNIVERSE_DOMAIN,
@@ -957,6 +996,38 @@ class TestCredentials(object):
client_id=None,
client_secret=None,
credential_source=self.CREDENTIAL_SOURCE,
+ aws_security_credentials_supplier=None,
+ quota_project_id=None,
+ workforce_pool_user_project=None,
+ universe_domain=DEFAULT_UNIVERSE_DOMAIN,
+ )
+
+ @mock.patch.object(aws.Credentials, "__init__", return_value=None)
+ def test_from_info_supplier(self, mock_init):
+ supplier = TestAwsSecurityCredentialsSupplier()
+
+ credentials = aws.Credentials.from_info(
+ {
+ "audience": AUDIENCE,
+ "subject_token_type": SUBJECT_TOKEN_TYPE,
+ "token_url": TOKEN_URL,
+ "aws_security_credentials_supplier": supplier,
+ }
+ )
+
+ # Confirm aws.Credentials instance initialized with the expected parameters.
+ assert isinstance(credentials, aws.Credentials)
+ mock_init.assert_called_once_with(
+ audience=AUDIENCE,
+ subject_token_type=SUBJECT_TOKEN_TYPE,
+ token_url=TOKEN_URL,
+ token_info_url=None,
+ service_account_impersonation_url=None,
+ service_account_impersonation_options={},
+ client_id=None,
+ client_secret=None,
+ credential_source=None,
+ aws_security_credentials_supplier=supplier,
quota_project_id=None,
workforce_pool_user_project=None,
universe_domain=DEFAULT_UNIVERSE_DOMAIN,
@@ -993,6 +1064,7 @@ class TestCredentials(object):
client_id=CLIENT_ID,
client_secret=CLIENT_SECRET,
credential_source=self.CREDENTIAL_SOURCE,
+ aws_security_credentials_supplier=None,
quota_project_id=QUOTA_PROJECT_ID,
workforce_pool_user_project=None,
universe_domain=DEFAULT_UNIVERSE_DOMAIN,
@@ -1022,6 +1094,7 @@ class TestCredentials(object):
client_id=None,
client_secret=None,
credential_source=self.CREDENTIAL_SOURCE,
+ aws_security_credentials_supplier=None,
quota_project_id=None,
workforce_pool_user_project=None,
universe_domain=DEFAULT_UNIVERSE_DOMAIN,
@@ -1036,6 +1109,27 @@ class TestCredentials(object):
assert excinfo.match(r"No valid AWS 'credential_source' provided")
+ def test_constructor_invalid_credential_source_and_supplier(self):
+ # Provide both a credential source and supplier.
+ with pytest.raises(ValueError) as excinfo:
+ self.make_credentials(
+ credential_source=self.CREDENTIAL_SOURCE,
+ aws_security_credentials_supplier="test",
+ )
+
+ assert excinfo.match(
+ r"AWS credential cannot have both a credential source and an AWS security credentials supplier."
+ )
+
+ def test_constructor_invalid_no_credential_source_or_supplier(self):
+ # Provide no credential source or supplier.
+ with pytest.raises(ValueError) as excinfo:
+ self.make_credentials()
+
+ assert excinfo.match(
+ r"A valid credential source or AWS security credentials supplier must be provided."
+ )
+
def test_constructor_invalid_environment_id(self):
# Provide invalid environment_id.
credential_source = self.CREDENTIAL_SOURCE.copy()
@@ -1158,11 +1252,7 @@ class TestCredentials(object):
subject_token = credentials.retrieve_subject_token(request)
assert subject_token == self.make_serialized_aws_signed_request(
- {
- "access_key_id": ACCESS_KEY_ID,
- "secret_access_key": SECRET_ACCESS_KEY,
- "security_token": TOKEN,
- }
+ aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN)
)
# Assert region request.
self.assert_aws_metadata_request_kwargs(
@@ -1231,11 +1321,7 @@ class TestCredentials(object):
subject_token = credentials.retrieve_subject_token(request)
assert subject_token == self.make_serialized_aws_signed_request(
- {
- "access_key_id": ACCESS_KEY_ID,
- "secret_access_key": SECRET_ACCESS_KEY,
- "security_token": TOKEN,
- }
+ aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN)
)
# Assert session token request
self.assert_aws_metadata_request_kwargs(
@@ -1250,15 +1336,22 @@ class TestCredentials(object):
REGION_URL,
{"X-aws-ec2-metadata-token": self.AWS_IMDSV2_SESSION_TOKEN},
)
- # Assert role request.
+ # Assert session token request
self.assert_aws_metadata_request_kwargs(
request.call_args_list[2][1],
+ IMDSV2_SESSION_TOKEN_URL,
+ {"X-aws-ec2-metadata-token-ttl-seconds": "300"},
+ "PUT",
+ )
+ # Assert role request.
+ self.assert_aws_metadata_request_kwargs(
+ request.call_args_list[3][1],
SECURITY_CREDS_URL,
{"X-aws-ec2-metadata-token": self.AWS_IMDSV2_SESSION_TOKEN},
)
# Assert security credentials request.
self.assert_aws_metadata_request_kwargs(
- request.call_args_list[3][1],
+ request.call_args_list[4][1],
"{}/{}".format(SECURITY_CREDS_URL, self.AWS_ROLE),
{
"Content-Type": "application/json",
@@ -1335,11 +1428,7 @@ class TestCredentials(object):
subject_token = credentials.retrieve_subject_token(request)
assert subject_token == self.make_serialized_aws_signed_request(
- {
- "access_key_id": ACCESS_KEY_ID,
- "secret_access_key": SECRET_ACCESS_KEY,
- "security_token": TOKEN,
- }
+ aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN)
)
# Assert session token request.
self.assert_aws_metadata_request_kwargs(
@@ -1396,11 +1485,7 @@ class TestCredentials(object):
subject_token = credentials.retrieve_subject_token(request)
assert subject_token == self.make_serialized_aws_signed_request(
- {
- "access_key_id": ACCESS_KEY_ID,
- "secret_access_key": SECRET_ACCESS_KEY,
- "security_token": TOKEN,
- }
+ aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN)
)
# Assert session token request.
self.assert_aws_metadata_request_kwargs(
@@ -1451,11 +1536,7 @@ class TestCredentials(object):
subject_token = credentials.retrieve_subject_token(request)
assert subject_token == self.make_serialized_aws_signed_request(
- {
- "access_key_id": ACCESS_KEY_ID,
- "secret_access_key": SECRET_ACCESS_KEY,
- "security_token": TOKEN,
- }
+ aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN)
)
# Assert session token request.
self.assert_aws_metadata_request_kwargs(
@@ -1530,11 +1611,7 @@ class TestCredentials(object):
subject_token = credentials.retrieve_subject_token(request)
assert subject_token == self.make_serialized_aws_signed_request(
- {
- "access_key_id": ACCESS_KEY_ID,
- "secret_access_key": SECRET_ACCESS_KEY,
- "security_token": TOKEN,
- }
+ aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN)
)
# Assert session token request.
self.assert_aws_metadata_request_kwargs(
@@ -1549,15 +1626,22 @@ class TestCredentials(object):
REGION_URL_IPV6,
{"X-aws-ec2-metadata-token": self.AWS_IMDSV2_SESSION_TOKEN},
)
- # Assert role request.
+ # Assert session token request.
self.assert_aws_metadata_request_kwargs(
request.call_args_list[2][1],
+ IMDSV2_SESSION_TOKEN_URL_IPV6,
+ {"X-aws-ec2-metadata-token-ttl-seconds": "300"},
+ "PUT",
+ )
+ # Assert role request.
+ self.assert_aws_metadata_request_kwargs(
+ request.call_args_list[3][1],
SECURITY_CREDS_URL_IPV6,
{"X-aws-ec2-metadata-token": self.AWS_IMDSV2_SESSION_TOKEN},
)
# Assert security credentials request.
self.assert_aws_metadata_request_kwargs(
- request.call_args_list[3][1],
+ request.call_args_list[4][1],
"{}/{}".format(SECURITY_CREDS_URL_IPV6, self.AWS_ROLE),
{
"Content-Type": "application/json",
@@ -1619,7 +1703,7 @@ class TestCredentials(object):
subject_token = credentials.retrieve_subject_token(request)
assert subject_token == self.make_serialized_aws_signed_request(
- {"access_key_id": ACCESS_KEY_ID, "secret_access_key": SECRET_ACCESS_KEY}
+ aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY)
)
@mock.patch("google.auth._helpers.utcnow")
@@ -1636,11 +1720,7 @@ class TestCredentials(object):
subject_token = credentials.retrieve_subject_token(None)
assert subject_token == self.make_serialized_aws_signed_request(
- {
- "access_key_id": ACCESS_KEY_ID,
- "secret_access_key": SECRET_ACCESS_KEY,
- "security_token": TOKEN,
- }
+ aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN)
)
@mock.patch("google.auth._helpers.utcnow")
@@ -1659,11 +1739,7 @@ class TestCredentials(object):
subject_token = credentials.retrieve_subject_token(None)
assert subject_token == self.make_serialized_aws_signed_request(
- {
- "access_key_id": ACCESS_KEY_ID,
- "secret_access_key": SECRET_ACCESS_KEY,
- "security_token": TOKEN,
- }
+ aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN)
)
@mock.patch("google.auth._helpers.utcnow")
@@ -1686,11 +1762,7 @@ class TestCredentials(object):
subject_token = credentials.retrieve_subject_token(None)
assert subject_token == self.make_serialized_aws_signed_request(
- {
- "access_key_id": ACCESS_KEY_ID,
- "secret_access_key": SECRET_ACCESS_KEY,
- "security_token": TOKEN,
- }
+ aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN)
)
@mock.patch("google.auth._helpers.utcnow")
@@ -1708,7 +1780,7 @@ class TestCredentials(object):
subject_token = credentials.retrieve_subject_token(None)
assert subject_token == self.make_serialized_aws_signed_request(
- {"access_key_id": ACCESS_KEY_ID, "secret_access_key": SECRET_ACCESS_KEY}
+ aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY)
)
@mock.patch("google.auth._helpers.utcnow")
@@ -1730,11 +1802,7 @@ class TestCredentials(object):
subject_token = credentials.retrieve_subject_token(request)
assert subject_token == self.make_serialized_aws_signed_request(
- {
- "access_key_id": ACCESS_KEY_ID,
- "secret_access_key": SECRET_ACCESS_KEY,
- "security_token": TOKEN,
- }
+ aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN)
)
def test_retrieve_subject_token_error_determining_aws_region(self):
@@ -1806,11 +1874,7 @@ class TestCredentials(object):
self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
)
expected_subject_token = self.make_serialized_aws_signed_request(
- {
- "access_key_id": ACCESS_KEY_ID,
- "secret_access_key": SECRET_ACCESS_KEY,
- "security_token": TOKEN,
- }
+ aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN)
)
token_headers = {
"Content-Type": "application/x-www-form-urlencoded",
@@ -1869,11 +1933,7 @@ class TestCredentials(object):
self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
)
expected_subject_token = self.make_serialized_aws_signed_request(
- {
- "access_key_id": ACCESS_KEY_ID,
- "secret_access_key": SECRET_ACCESS_KEY,
- "security_token": TOKEN,
- }
+ aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN)
)
token_headers = {
"Content-Type": "application/x-www-form-urlencoded",
@@ -1939,11 +1999,7 @@ class TestCredentials(object):
_helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=3600)
).isoformat("T") + "Z"
expected_subject_token = self.make_serialized_aws_signed_request(
- {
- "access_key_id": ACCESS_KEY_ID,
- "secret_access_key": SECRET_ACCESS_KEY,
- "security_token": TOKEN,
- }
+ aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN)
)
token_headers = {
"Content-Type": "application/x-www-form-urlencoded",
@@ -2036,11 +2092,7 @@ class TestCredentials(object):
_helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=3600)
).isoformat("T") + "Z"
expected_subject_token = self.make_serialized_aws_signed_request(
- {
- "access_key_id": ACCESS_KEY_ID,
- "secret_access_key": SECRET_ACCESS_KEY,
- "security_token": TOKEN,
- }
+ aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN)
)
token_headers = {
"Content-Type": "application/x-www-form-urlencoded",
@@ -2122,3 +2174,249 @@ class TestCredentials(object):
credentials.refresh(request)
assert excinfo.match(r"Unable to retrieve AWS region")
+
+ @mock.patch("google.auth._helpers.utcnow")
+ def test_retrieve_subject_token_success_with_supplier(self, utcnow):
+ utcnow.return_value = datetime.datetime.strptime(
+ self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
+ )
+ request = self.make_mock_request()
+
+ security_credentials = aws.AwsSecurityCredentials(
+ ACCESS_KEY_ID, SECRET_ACCESS_KEY
+ )
+ supplier = TestAwsSecurityCredentialsSupplier(
+ security_credentials=security_credentials, region=self.AWS_REGION
+ )
+
+ credentials = self.make_credentials(aws_security_credentials_supplier=supplier)
+
+ subject_token = credentials.retrieve_subject_token(request)
+ assert subject_token == self.make_serialized_aws_signed_request(
+ aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY)
+ )
+
+ @mock.patch("google.auth._helpers.utcnow")
+ def test_retrieve_subject_token_success_with_supplier_session_token(self, utcnow):
+ utcnow.return_value = datetime.datetime.strptime(
+ self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
+ )
+ request = self.make_mock_request()
+
+ security_credentials = aws.AwsSecurityCredentials(
+ ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN
+ )
+ supplier = TestAwsSecurityCredentialsSupplier(
+ security_credentials=security_credentials, region=self.AWS_REGION
+ )
+
+ credentials = self.make_credentials(aws_security_credentials_supplier=supplier)
+
+ subject_token = credentials.retrieve_subject_token(request)
+ assert subject_token == self.make_serialized_aws_signed_request(
+ aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN)
+ )
+
+ @mock.patch("google.auth._helpers.utcnow")
+ def test_retrieve_subject_token_success_with_supplier_correct_context(self, utcnow):
+ utcnow.return_value = datetime.datetime.strptime(
+ self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
+ )
+ request = self.make_mock_request()
+ expected_context = external_account.SupplierContext(
+ SUBJECT_TOKEN_TYPE, AUDIENCE
+ )
+
+ security_credentials = aws.AwsSecurityCredentials(
+ ACCESS_KEY_ID, SECRET_ACCESS_KEY
+ )
+ supplier = TestAwsSecurityCredentialsSupplier(
+ security_credentials=security_credentials,
+ region=self.AWS_REGION,
+ expected_context=expected_context,
+ )
+
+ credentials = self.make_credentials(aws_security_credentials_supplier=supplier)
+
+ credentials.retrieve_subject_token(request)
+
+ def test_retrieve_subject_token_error_with_supplier(self):
+ request = self.make_mock_request()
+ expected_exception = exceptions.RefreshError("Test error")
+ supplier = TestAwsSecurityCredentialsSupplier(
+ region=self.AWS_REGION, credentials_exception=expected_exception
+ )
+
+ credentials = self.make_credentials(aws_security_credentials_supplier=supplier)
+
+ with pytest.raises(exceptions.RefreshError) as excinfo:
+ credentials.refresh(request)
+
+ assert excinfo.match(r"Test error")
+
+ def test_retrieve_subject_token_error_with_supplier_region(self):
+ request = self.make_mock_request()
+ expected_exception = exceptions.RefreshError("Test error")
+ security_credentials = aws.AwsSecurityCredentials(
+ ACCESS_KEY_ID, SECRET_ACCESS_KEY
+ )
+ supplier = TestAwsSecurityCredentialsSupplier(
+ security_credentials=security_credentials,
+ region_exception=expected_exception,
+ )
+
+ credentials = self.make_credentials(aws_security_credentials_supplier=supplier)
+
+ with pytest.raises(exceptions.RefreshError) as excinfo:
+ credentials.refresh(request)
+
+ assert excinfo.match(r"Test error")
+
+ @mock.patch(
+ "google.auth.metrics.python_and_auth_lib_version",
+ return_value=LANG_LIBRARY_METRICS_HEADER_VALUE,
+ )
+ @mock.patch("google.auth._helpers.utcnow")
+ def test_refresh_success_with_supplier_with_impersonation(
+ self, utcnow, mock_auth_lib_value
+ ):
+ utcnow.return_value = datetime.datetime.strptime(
+ self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
+ )
+ expire_time = (
+ _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=3600)
+ ).isoformat("T") + "Z"
+ expected_subject_token = self.make_serialized_aws_signed_request(
+ aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN)
+ )
+ token_headers = {
+ "Content-Type": "application/x-www-form-urlencoded",
+ "Authorization": "Basic " + BASIC_AUTH_ENCODING,
+ "x-goog-api-client": "gl-python/3.7 auth/1.1 google-byoid-sdk sa-impersonation/true config-lifetime/false source/programmatic",
+ }
+ token_request_data = {
+ "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
+ "audience": AUDIENCE,
+ "requested_token_type": "urn:ietf:params:oauth:token-type:access_token",
+ "scope": "https://www.googleapis.com/auth/iam",
+ "subject_token": expected_subject_token,
+ "subject_token_type": SUBJECT_TOKEN_TYPE,
+ }
+ # Service account impersonation request/response.
+ impersonation_response = {
+ "accessToken": "SA_ACCESS_TOKEN",
+ "expireTime": expire_time,
+ }
+ impersonation_headers = {
+ "Content-Type": "application/json",
+ "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]),
+ "x-goog-user-project": QUOTA_PROJECT_ID,
+ "x-goog-api-client": IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE,
+ "x-allowed-locations": "0x0",
+ }
+ impersonation_request_data = {
+ "delegates": None,
+ "scope": SCOPES,
+ "lifetime": "3600s",
+ }
+ request = self.make_mock_request(
+ token_status=http_client.OK,
+ token_data=self.SUCCESS_RESPONSE,
+ impersonation_status=http_client.OK,
+ impersonation_data=impersonation_response,
+ )
+
+ supplier = TestAwsSecurityCredentialsSupplier(
+ security_credentials=aws.AwsSecurityCredentials(
+ ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN
+ ),
+ region=self.AWS_REGION,
+ )
+
+ credentials = self.make_credentials(
+ client_id=CLIENT_ID,
+ client_secret=CLIENT_SECRET,
+ aws_security_credentials_supplier=supplier,
+ service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL,
+ quota_project_id=QUOTA_PROJECT_ID,
+ scopes=SCOPES,
+ # Default scopes should be ignored.
+ default_scopes=["ignored"],
+ )
+
+ credentials.refresh(request)
+
+ assert len(request.call_args_list) == 2
+ # First request should be sent to GCP STS endpoint.
+ self.assert_token_request_kwargs(
+ request.call_args_list[0][1], token_headers, token_request_data
+ )
+ # Second request should be sent to iamcredentials endpoint for service
+ # account impersonation.
+ self.assert_impersonation_request_kwargs(
+ request.call_args_list[1][1],
+ impersonation_headers,
+ impersonation_request_data,
+ )
+ assert credentials.token == impersonation_response["accessToken"]
+ assert credentials.quota_project_id == QUOTA_PROJECT_ID
+ assert credentials.scopes == SCOPES
+ assert credentials.default_scopes == ["ignored"]
+
+ @mock.patch(
+ "google.auth.metrics.python_and_auth_lib_version",
+ return_value=LANG_LIBRARY_METRICS_HEADER_VALUE,
+ )
+ @mock.patch("google.auth._helpers.utcnow")
+ def test_refresh_success_with_supplier(self, utcnow, mock_auth_lib_value):
+ utcnow.return_value = datetime.datetime.strptime(
+ self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ"
+ )
+ expected_subject_token = self.make_serialized_aws_signed_request(
+ aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN)
+ )
+ token_headers = {
+ "Content-Type": "application/x-www-form-urlencoded",
+ "Authorization": "Basic " + BASIC_AUTH_ENCODING,
+ "x-goog-api-client": "gl-python/3.7 auth/1.1 google-byoid-sdk sa-impersonation/false config-lifetime/false source/programmatic",
+ }
+ token_request_data = {
+ "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
+ "audience": AUDIENCE,
+ "requested_token_type": "urn:ietf:params:oauth:token-type:access_token",
+ "scope": " ".join(SCOPES),
+ "subject_token": expected_subject_token,
+ "subject_token_type": SUBJECT_TOKEN_TYPE,
+ }
+ request = self.make_mock_request(
+ token_status=http_client.OK, token_data=self.SUCCESS_RESPONSE
+ )
+
+ supplier = TestAwsSecurityCredentialsSupplier(
+ security_credentials=aws.AwsSecurityCredentials(
+ ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN
+ ),
+ region=self.AWS_REGION,
+ )
+
+ credentials = self.make_credentials(
+ client_id=CLIENT_ID,
+ client_secret=CLIENT_SECRET,
+ aws_security_credentials_supplier=supplier,
+ quota_project_id=QUOTA_PROJECT_ID,
+ scopes=SCOPES,
+ # Default scopes should be ignored.
+ default_scopes=["ignored"],
+ )
+
+ credentials.refresh(request)
+
+ assert len(request.call_args_list) == 1
+ # First request should be sent to GCP STS endpoint.
+ self.assert_token_request_kwargs(
+ request.call_args_list[0][1], token_headers, token_request_data
+ )
+ assert credentials.token == self.SUCCESS_RESPONSE["access_token"]
+ assert credentials.quota_project_id == QUOTA_PROJECT_ID
+ assert credentials.scopes == SCOPES
+ assert credentials.default_scopes == ["ignored"]
diff --git a/contrib/python/google-auth/py3/tests/test_external_account.py b/contrib/python/google-auth/py3/tests/test_external_account.py
index 03a5014ce5b..c458b21b643 100644
--- a/contrib/python/google-auth/py3/tests/test_external_account.py
+++ b/contrib/python/google-auth/py3/tests/test_external_account.py
@@ -477,16 +477,6 @@ class TestCredentials(object):
universe_domain=DEFAULT_UNIVERSE_DOMAIN,
)
- def test_with_invalid_impersonation_target_principal(self):
- invalid_url = "https://iamcredentials.googleapis.com/v1/invalid"
-
- with pytest.raises(exceptions.RefreshError) as excinfo:
- self.make_credentials(service_account_impersonation_url=invalid_url)
-
- assert excinfo.match(
- r"Unable to determine target principal from service account impersonation URL."
- )
-
def test_info(self):
credentials = self.make_credentials(universe_domain="dummy_universe.com")
@@ -1069,6 +1059,21 @@ class TestCredentials(object):
assert not credentials.expired
assert credentials.token is None
+ def test_refresh_impersonation_invalid_impersonated_url_error(self):
+ credentials = self.make_credentials(
+ service_account_impersonation_url="https://iamcredentials.googleapis.com/v1/invalid",
+ scopes=self.SCOPES,
+ )
+
+ with pytest.raises(exceptions.RefreshError) as excinfo:
+ credentials.refresh(None)
+
+ assert excinfo.match(
+ r"Unable to determine target principal from service account impersonation URL."
+ )
+ assert not credentials.expired
+ assert credentials.token is None
+
@mock.patch(
"google.auth.metrics.python_and_auth_lib_version",
return_value=LANG_LIBRARY_METRICS_HEADER_VALUE,
@@ -1913,3 +1918,10 @@ class TestCredentials(object):
assert project_id is None
# Only 2 requests to STS and cloud resource manager should be sent.
assert len(request.call_args_list) == 2
+
+
+def test_supplier_context():
+ context = external_account.SupplierContext("TestTokenType", "TestAudience")
+
+ assert context.subject_token_type == "TestTokenType"
+ assert context.audience == "TestAudience"
diff --git a/contrib/python/google-auth/py3/tests/test_identity_pool.py b/contrib/python/google-auth/py3/tests/test_identity_pool.py
index 96be1d61c28..0de711832f6 100644
--- a/contrib/python/google-auth/py3/tests/test_identity_pool.py
+++ b/contrib/python/google-auth/py3/tests/test_identity_pool.py
@@ -21,7 +21,7 @@ import urllib
import mock
import pytest # type: ignore
-from google.auth import _helpers
+from google.auth import _helpers, external_account
from google.auth import exceptions
from google.auth import identity_pool
from google.auth import metrics
@@ -152,6 +152,22 @@ INVALID_SERVICE_ACCOUNT_IMPERSONATION_URLS = [
]
+class TestSubjectTokenSupplier(identity_pool.SubjectTokenSupplier):
+ def __init__(
+ self, subject_token=None, subject_token_exception=None, expected_context=None
+ ):
+ self._subject_token = subject_token
+ self._subject_token_exception = subject_token_exception
+ self._expected_context = expected_context
+
+ def get_subject_token(self, context, request):
+ if self._expected_context is not None:
+ assert self._expected_context == context
+ if self._subject_token_exception is not None:
+ raise self._subject_token_exception
+ return self._subject_token
+
+
class TestCredentials(object):
CREDENTIAL_SOURCE_TEXT = {"file": SUBJECT_TOKEN_TEXT_FILE}
CREDENTIAL_SOURCE_JSON = {
@@ -274,10 +290,13 @@ class TestCredentials(object):
else:
metrics_options["sa-impersonation"] = "false"
metrics_options["config-lifetime"] = "false"
- if credentials._credential_source_file:
- metrics_options["source"] = "file"
+ if credentials._credential_source:
+ if credentials._credential_source_file:
+ metrics_options["source"] = "file"
+ else:
+ metrics_options["source"] = "url"
else:
- metrics_options["source"] = "url"
+ metrics_options["source"] = "programmatic"
token_headers["x-goog-api-client"] = metrics.byoid_metrics_header(
metrics_options
@@ -387,6 +406,7 @@ class TestCredentials(object):
default_scopes=None,
service_account_impersonation_url=None,
credential_source=None,
+ subject_token_supplier=None,
workforce_pool_user_project=None,
):
return identity_pool.Credentials(
@@ -396,6 +416,7 @@ class TestCredentials(object):
token_info_url=token_info_url,
service_account_impersonation_url=service_account_impersonation_url,
credential_source=credential_source,
+ subject_token_supplier=subject_token_supplier,
client_id=client_id,
client_secret=client_secret,
quota_project_id=quota_project_id,
@@ -433,6 +454,7 @@ class TestCredentials(object):
client_id=CLIENT_ID,
client_secret=CLIENT_SECRET,
credential_source=self.CREDENTIAL_SOURCE_TEXT,
+ subject_token_supplier=None,
quota_project_id=QUOTA_PROJECT_ID,
workforce_pool_user_project=None,
universe_domain=DEFAULT_UNIVERSE_DOMAIN,
@@ -461,6 +483,38 @@ class TestCredentials(object):
client_id=None,
client_secret=None,
credential_source=self.CREDENTIAL_SOURCE_TEXT,
+ subject_token_supplier=None,
+ quota_project_id=None,
+ workforce_pool_user_project=None,
+ universe_domain=DEFAULT_UNIVERSE_DOMAIN,
+ )
+
+ @mock.patch.object(identity_pool.Credentials, "__init__", return_value=None)
+ def test_from_info_supplier(self, mock_init):
+ supplier = TestSubjectTokenSupplier()
+
+ credentials = identity_pool.Credentials.from_info(
+ {
+ "audience": AUDIENCE,
+ "subject_token_type": SUBJECT_TOKEN_TYPE,
+ "token_url": TOKEN_URL,
+ "subject_token_supplier": supplier,
+ }
+ )
+
+ # Confirm identity_pool.Credentials instantiated with expected attributes.
+ assert isinstance(credentials, identity_pool.Credentials)
+ mock_init.assert_called_once_with(
+ audience=AUDIENCE,
+ subject_token_type=SUBJECT_TOKEN_TYPE,
+ token_url=TOKEN_URL,
+ token_info_url=None,
+ service_account_impersonation_url=None,
+ service_account_impersonation_options={},
+ client_id=None,
+ client_secret=None,
+ credential_source=None,
+ subject_token_supplier=supplier,
quota_project_id=None,
workforce_pool_user_project=None,
universe_domain=DEFAULT_UNIVERSE_DOMAIN,
@@ -490,6 +544,7 @@ class TestCredentials(object):
client_id=None,
client_secret=None,
credential_source=self.CREDENTIAL_SOURCE_TEXT,
+ subject_token_supplier=None,
quota_project_id=None,
workforce_pool_user_project=WORKFORCE_POOL_USER_PROJECT,
universe_domain=DEFAULT_UNIVERSE_DOMAIN,
@@ -525,6 +580,7 @@ class TestCredentials(object):
client_id=CLIENT_ID,
client_secret=CLIENT_SECRET,
credential_source=self.CREDENTIAL_SOURCE_TEXT,
+ subject_token_supplier=None,
quota_project_id=QUOTA_PROJECT_ID,
workforce_pool_user_project=None,
universe_domain=DEFAULT_UNIVERSE_DOMAIN,
@@ -554,6 +610,7 @@ class TestCredentials(object):
client_id=None,
client_secret=None,
credential_source=self.CREDENTIAL_SOURCE_TEXT,
+ subject_token_supplier=None,
quota_project_id=None,
workforce_pool_user_project=None,
universe_domain=DEFAULT_UNIVERSE_DOMAIN,
@@ -584,6 +641,7 @@ class TestCredentials(object):
client_id=None,
client_secret=None,
credential_source=self.CREDENTIAL_SOURCE_TEXT,
+ subject_token_supplier=None,
quota_project_id=None,
workforce_pool_user_project=WORKFORCE_POOL_USER_PROJECT,
universe_domain=DEFAULT_UNIVERSE_DOMAIN,
@@ -634,7 +692,29 @@ class TestCredentials(object):
with pytest.raises(ValueError) as excinfo:
self.make_credentials(credential_source="non-dict")
- assert excinfo.match(r"Missing credential_source")
+ assert excinfo.match(
+ r"Invalid credential_source. The credential_source is not a dict."
+ )
+
+ def test_constructor_invalid_no_credential_source_or_supplier(self):
+ with pytest.raises(ValueError) as excinfo:
+ self.make_credentials()
+
+ assert excinfo.match(
+ r"A valid credential source or a subject token supplier must be provided."
+ )
+
+ def test_constructor_invalid_both_credential_source_and_supplier(self):
+ supplier = TestSubjectTokenSupplier()
+ with pytest.raises(ValueError) as excinfo:
+ self.make_credentials(
+ credential_source=self.CREDENTIAL_SOURCE_TEXT,
+ subject_token_supplier=supplier,
+ )
+
+ assert excinfo.match(
+ r"Identity pool credential cannot have both a credential source and a subject token supplier."
+ )
def test_constructor_invalid_credential_source_format_type(self):
credential_source = {"format": {"type": "xml"}}
@@ -1298,3 +1378,78 @@ class TestCredentials(object):
self.CREDENTIAL_URL, "not_found"
)
)
+
+ def test_retrieve_subject_token_supplier(self):
+ supplier = TestSubjectTokenSupplier(subject_token=JSON_FILE_SUBJECT_TOKEN)
+
+ credentials = self.make_credentials(subject_token_supplier=supplier)
+
+ subject_token = credentials.retrieve_subject_token(None)
+
+ assert subject_token == JSON_FILE_SUBJECT_TOKEN
+
+ def test_retrieve_subject_token_supplier_correct_context(self):
+ supplier = TestSubjectTokenSupplier(
+ subject_token=JSON_FILE_SUBJECT_TOKEN,
+ expected_context=external_account.SupplierContext(
+ SUBJECT_TOKEN_TYPE, AUDIENCE
+ ),
+ )
+
+ credentials = self.make_credentials(subject_token_supplier=supplier)
+
+ credentials.retrieve_subject_token(None)
+
+ def test_retrieve_subject_token_supplier_error(self):
+ expected_exception = exceptions.RefreshError("test error")
+ supplier = TestSubjectTokenSupplier(subject_token_exception=expected_exception)
+
+ credentials = self.make_credentials(subject_token_supplier=supplier)
+
+ with pytest.raises(exceptions.RefreshError) as excinfo:
+ credentials.refresh(self.make_mock_request(token_data=JSON_FILE_CONTENT))
+
+ assert excinfo.match("test error")
+
+ def test_refresh_success_supplier_with_impersonation_url(self):
+ # Initialize credentials with service account impersonation and a supplier.
+ supplier = TestSubjectTokenSupplier(subject_token=JSON_FILE_SUBJECT_TOKEN)
+ credentials = self.make_credentials(
+ subject_token_supplier=supplier,
+ service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL,
+ scopes=SCOPES,
+ )
+
+ self.assert_underlying_credentials_refresh(
+ credentials=credentials,
+ audience=AUDIENCE,
+ subject_token=TEXT_FILE_SUBJECT_TOKEN,
+ subject_token_type=SUBJECT_TOKEN_TYPE,
+ token_url=TOKEN_URL,
+ service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL,
+ basic_auth_encoding=None,
+ quota_project_id=None,
+ used_scopes=SCOPES,
+ scopes=SCOPES,
+ default_scopes=None,
+ )
+
+ def test_refresh_success_supplier_without_impersonation_url(self):
+ # Initialize supplier credentials without service account impersonation.
+ supplier = TestSubjectTokenSupplier(subject_token=JSON_FILE_SUBJECT_TOKEN)
+ credentials = self.make_credentials(
+ subject_token_supplier=supplier, scopes=SCOPES
+ )
+
+ self.assert_underlying_credentials_refresh(
+ credentials=credentials,
+ audience=AUDIENCE,
+ subject_token=TEXT_FILE_SUBJECT_TOKEN,
+ subject_token_type=SUBJECT_TOKEN_TYPE,
+ token_url=TOKEN_URL,
+ basic_auth_encoding=None,
+ quota_project_id=None,
+ used_scopes=SCOPES,
+ scopes=SCOPES,
+ default_scopes=None,
+ )
diff --git a/contrib/python/google-auth/py3/ya.make b/contrib/python/google-auth/py3/ya.make
index 6b3decfc4ec..952d1ebdd3a 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.28.2)
+VERSION(2.29.0)
LICENSE(Apache-2.0)
diff --git a/contrib/python/hypothesis/py3/.dist-info/METADATA b/contrib/python/hypothesis/py3/.dist-info/METADATA
index c61fb5cb387..17a8050e565 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.99.9
+Version: 6.99.12
Summary: A library for property-based testing
Home-page: https://hypothesis.works
Author: David R. MacIver and Zac Hatfield-Dodds
diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/data.py b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/data.py
index c35b533ecb4..3f15a974efc 100644
--- a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/data.py
+++ b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/data.py
@@ -56,6 +56,7 @@ from hypothesis.internal.floats import (
SIGNALING_NAN,
SMALLEST_SUBNORMAL,
float_to_int,
+ int_to_float,
make_float_clamper,
next_down,
next_up,
@@ -998,13 +999,29 @@ class IRNode:
return self.value == shrink_towards
if self.ir_type == "float":
- # floats shrink "like integers" (for now, anyway), except shrink_towards
- # is not configurable and is always 0.
+ min_value = self.kwargs["min_value"]
+ max_value = self.kwargs["max_value"]
shrink_towards = 0
- shrink_towards = max(self.kwargs["min_value"], shrink_towards)
- shrink_towards = min(self.kwargs["max_value"], shrink_towards)
- return ir_value_equal("float", self.value, shrink_towards)
+ if min_value == -math.inf and max_value == math.inf:
+ return ir_value_equal("float", self.value, shrink_towards)
+
+ if (
+ not math.isinf(min_value)
+ and not math.isinf(max_value)
+ and math.ceil(min_value) <= math.floor(max_value)
+ ):
+ # the interval contains an integer. the simplest integer is the
+ # one closest to shrink_towards
+ shrink_towards = max(math.ceil(min_value), shrink_towards)
+ shrink_towards = min(math.floor(max_value), shrink_towards)
+ return ir_value_equal("float", self.value, shrink_towards)
+
+ # the real answer here is "the value in [min_value, max_value] with
+ # the lowest denominator when represented as a fraction".
+ # It would be good to compute this correctly in the future, but it's
+ # also not incorrect to be conservative here.
+ return False
if self.ir_type == "boolean":
return self.value is False
if self.ir_type == "string":
@@ -1481,6 +1498,27 @@ class HypothesisProvider(PrimitiveProvider):
result = clamped
else:
result = nasty_floats[i - 1]
+ # nan values generated via int_to_float break list membership:
+ #
+ # >>> n = 18444492273895866368
+ # >>> assert math.isnan(int_to_float(n))
+ # >>> assert int_to_float(n) not in [int_to_float(n)]
+ #
+ # because int_to_float nans are not equal in the sense of either
+ # `a == b` or `a is b`.
+ #
+ # This can lead to flaky errors when collections require unique
+ # floats. I think what is happening is that in some places we
+ # provide math.nan, and in others we provide
+ # int_to_float(float_to_int(math.nan)), and which one gets used
+ # is not deterministic across test iterations.
+ #
+ # As a (temporary?) fix, we'll *always* generate nan values which
+ # are not equal in the identity sense.
+ #
+ # see also https://github.com/HypothesisWorks/hypothesis/issues/3926.
+ if math.isnan(result):
+ result = int_to_float(float_to_int(result))
self._draw_float(forced=result, fake_forced=fake_forced)
diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/shrinker.py b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/shrinker.py
index ce232c67f1f..ae28fcd7419 100644
--- a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/shrinker.py
+++ b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/shrinker.py
@@ -1194,7 +1194,6 @@ class Shrinker:
block,
lambda b: self.try_shrinking_blocks(targets, b),
random=self.random,
- full=False,
)
@defines_shrink_pass()
@@ -1217,7 +1216,7 @@ class Shrinker:
node = chooser.choose(
self.nodes,
- lambda node: node.ir_type == "float" and not node.was_forced
+ lambda node: node.ir_type == "float" and not node.trivial
# avoid shrinking integer-valued floats. In our current ordering, these
# are already simpler than all other floats, so it's better to shrink
# them in other passes.
@@ -1364,7 +1363,6 @@ class Shrinker:
self.shrink_target.buffer[u:v],
lambda b: self.try_shrinking_blocks((i,), b),
random=self.random,
- full=False,
)
if self.shrink_target is not initial:
diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/reflection.py b/contrib/python/hypothesis/py3/hypothesis/internal/reflection.py
index a829f097be5..c9ffa6eb2da 100644
--- a/contrib/python/hypothesis/py3/hypothesis/internal/reflection.py
+++ b/contrib/python/hypothesis/py3/hypothesis/internal/reflection.py
@@ -14,6 +14,7 @@ to really unreasonable lengths to produce pretty output."""
import ast
import hashlib
import inspect
+import linecache
import os
import re
import sys
@@ -315,6 +316,10 @@ def extract_lambda_source(f):
sig = inspect.signature(f)
assert sig.return_annotation in (inspect.Parameter.empty, None), sig
+ # Using pytest-xdist on Python 3.13, there's an entry in the linecache for
+ # file "<string>", which then returns nonsense to getsource. Discard it.
+ linecache.cache.pop("<string>", None)
+
if sig.parameters:
if_confused = f"lambda {str(sig)[1:-1]}: <unknown>"
else:
@@ -329,7 +334,7 @@ def extract_lambda_source(f):
source = source.strip()
if "lambda" not in source and sys.platform == "emscripten": # pragma: no cover
return if_confused # work around Pyodide bug in inspect.getsource()
- assert "lambda" in source
+ assert "lambda" in source, source
tree = None
diff --git a/contrib/python/hypothesis/py3/hypothesis/version.py b/contrib/python/hypothesis/py3/hypothesis/version.py
index 670380b6195..14d8902f651 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, 99, 9)
+__version_info__ = (6, 99, 12)
__version__ = ".".join(map(str, __version_info__))
diff --git a/contrib/python/hypothesis/py3/ya.make b/contrib/python/hypothesis/py3/ya.make
index f6d2f61d06c..604d175b405 100644
--- a/contrib/python/hypothesis/py3/ya.make
+++ b/contrib/python/hypothesis/py3/ya.make
@@ -2,7 +2,7 @@
PY3_LIBRARY()
-VERSION(6.99.9)
+VERSION(6.99.12)
LICENSE(MPL-2.0)
diff --git a/contrib/python/importlib-metadata/py3/.dist-info/METADATA b/contrib/python/importlib-metadata/py3/.dist-info/METADATA
index 38ffc3a5f08..ec8a77c1163 100644
--- a/contrib/python/importlib-metadata/py3/.dist-info/METADATA
+++ b/contrib/python/importlib-metadata/py3/.dist-info/METADATA
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: importlib_metadata
-Version: 7.0.2
+Version: 7.1.0
Summary: Read metadata from Python packages
Home-page: https://github.com/python/importlib_metadata
Author: Jason R. Coombs
@@ -32,6 +32,7 @@ Requires-Dist: packaging ; extra == 'testing'
Requires-Dist: pyfakefs ; extra == 'testing'
Requires-Dist: flufl.flake8 ; extra == 'testing'
Requires-Dist: pytest-perf >=0.9.2 ; extra == 'testing'
+Requires-Dist: jaraco.test >=5.4 ; extra == 'testing'
Requires-Dist: pytest-mypy ; (platform_python_implementation != "PyPy") and extra == 'testing'
Requires-Dist: importlib-resources >=1.3 ; (python_version < "3.9") and extra == 'testing'
diff --git a/contrib/python/importlib-metadata/py3/importlib_metadata/__init__.py b/contrib/python/importlib-metadata/py3/importlib_metadata/__init__.py
index a069efe55f4..0ed3e8dfb5b 100644
--- a/contrib/python/importlib-metadata/py3/importlib_metadata/__init__.py
+++ b/contrib/python/importlib-metadata/py3/importlib_metadata/__init__.py
@@ -3,7 +3,6 @@ from __future__ import annotations
import os
import re
import abc
-import csv
import sys
import json
import email
@@ -18,7 +17,8 @@ import itertools
import posixpath
import collections
-from . import _adapters, _meta, _py39compat
+from . import _adapters, _meta
+from .compat import py39
from ._collections import FreezableDefaultDict, Pair
from ._compat import (
NullFinder,
@@ -285,7 +285,7 @@ class EntryPoints(tuple):
Select entry points from self that match the
given parameters (typically group and/or name).
"""
- return EntryPoints(ep for ep in self if _py39compat.ep_matches(ep, **params))
+ return EntryPoints(ep for ep in self if py39.ep_matches(ep, **params))
@property
def names(self) -> Set[str]:
@@ -527,6 +527,10 @@ class Distribution(DeprecatedNonAbstract):
@pass_none
def make_files(lines):
+ # Delay csv import, since Distribution.files is not as widely used
+ # as other parts of importlib.metadata
+ import csv
+
return starmap(make_file, csv.reader(lines))
@pass_none
@@ -878,8 +882,9 @@ class MetadataPathFinder(NullFinder, DistributionFinder):
of Python that do not have a PathFinder find_distributions().
"""
+ @classmethod
def find_distributions(
- self, context=DistributionFinder.Context()
+ cls, context=DistributionFinder.Context()
) -> Iterable[PathDistribution]:
"""
Find distributions.
@@ -889,7 +894,7 @@ class MetadataPathFinder(NullFinder, DistributionFinder):
(or all names if ``None`` indicated) along the paths in the list
of directories ``context.path``.
"""
- found = self._search_paths(context.name, context.path)
+ found = cls._search_paths(context.name, context.path)
return map(PathDistribution, found)
@classmethod
@@ -900,6 +905,7 @@ class MetadataPathFinder(NullFinder, DistributionFinder):
path.search(prepared) for path in map(FastPath, paths)
)
+ @classmethod
def invalidate_caches(cls) -> None:
FastPath.__new__.cache_clear()
@@ -1054,7 +1060,7 @@ def version(distribution_name: str) -> str:
_unique = functools.partial(
unique_everseen,
- key=_py39compat.normalized_name,
+ key=py39.normalized_name,
)
"""
Wrapper for ``distributions`` to return unique distributions by name.
diff --git a/contrib/python/importlib-metadata/py3/importlib_metadata/compat/__init__.py b/contrib/python/importlib-metadata/py3/importlib_metadata/compat/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/contrib/python/importlib-metadata/py3/importlib_metadata/compat/__init__.py
diff --git a/contrib/python/importlib-metadata/py3/importlib_metadata/_py39compat.py b/contrib/python/importlib-metadata/py3/importlib_metadata/compat/py39.py
index fc6b82216f8..1f15bd97e6a 100644
--- a/contrib/python/importlib-metadata/py3/importlib_metadata/_py39compat.py
+++ b/contrib/python/importlib-metadata/py3/importlib_metadata/compat/py39.py
@@ -6,7 +6,7 @@ from typing import TYPE_CHECKING, Any, Optional
if TYPE_CHECKING: # pragma: no cover
# Prevent circular imports on runtime.
- from . import Distribution, EntryPoint
+ from .. import Distribution, EntryPoint
else:
Distribution = EntryPoint = Any
@@ -18,7 +18,7 @@ def normalized_name(dist: Distribution) -> Optional[str]:
try:
return dist._normalized_name
except AttributeError:
- from . import Prepared # -> delay to prevent circular imports.
+ from .. import Prepared # -> delay to prevent circular imports.
return Prepared.normalize(getattr(dist, "name", None) or dist.metadata['Name'])
@@ -30,7 +30,7 @@ def ep_matches(ep: EntryPoint, **params) -> bool:
try:
return ep.matches(**params)
except AttributeError:
- from . import EntryPoint # -> delay to prevent circular imports.
+ from .. import EntryPoint # -> delay to prevent circular imports.
# Reconstruct the EntryPoint object to make sure it is compatible.
return EntryPoint(ep.name, ep.value, ep.group).matches(**params)
diff --git a/contrib/python/importlib-metadata/py3/ya.make b/contrib/python/importlib-metadata/py3/ya.make
index 962408f997b..00d69e1c375 100644
--- a/contrib/python/importlib-metadata/py3/ya.make
+++ b/contrib/python/importlib-metadata/py3/ya.make
@@ -2,7 +2,7 @@
PY3_LIBRARY()
-VERSION(7.0.2)
+VERSION(7.1.0)
LICENSE(Apache-2.0)
@@ -21,8 +21,9 @@ PY_SRCS(
importlib_metadata/_functools.py
importlib_metadata/_itertools.py
importlib_metadata/_meta.py
- importlib_metadata/_py39compat.py
importlib_metadata/_text.py
+ importlib_metadata/compat/__init__.py
+ importlib_metadata/compat/py39.py
importlib_metadata/diagnose.py
)
diff --git a/contrib/python/lz4/py2/.dist-info/METADATA b/contrib/python/lz4/py2/.dist-info/METADATA
index a896017502e..1f76df926c9 100644
--- a/contrib/python/lz4/py2/.dist-info/METADATA
+++ b/contrib/python/lz4/py2/.dist-info/METADATA
@@ -18,9 +18,10 @@ Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
+Provides-Extra: docs
Provides-Extra: tests
Provides-Extra: flake8
-Provides-Extra: docs
+Requires-Dist: future
Provides-Extra: docs
Requires-Dist: sphinx (>=1.6.0); extra == 'docs'
Requires-Dist: sphinx-bootstrap-theme; extra == 'docs'
diff --git a/contrib/python/numpy/py3/numpy/__init__.py b/contrib/python/numpy/py3/numpy/__init__.py
index a2fbd368beb..91da496a952 100644
--- a/contrib/python/numpy/py3/numpy/__init__.py
+++ b/contrib/python/numpy/py3/numpy/__init__.py
@@ -390,7 +390,7 @@ else:
if sys.platform == "darwin":
from . import exceptions
with warnings.catch_warnings(record=True) as w:
- #_mac_os_check()
+ _mac_os_check()
# Throw runtime error, if the test failed Check for warning and error_message
if len(w) > 0:
for _wn in w:
diff --git a/contrib/python/pytest-mock/py3/.dist-info/METADATA b/contrib/python/pytest-mock/py3/.dist-info/METADATA
index 2d35e954469..1ffa498c292 100644
--- a/contrib/python/pytest-mock/py3/.dist-info/METADATA
+++ b/contrib/python/pytest-mock/py3/.dist-info/METADATA
@@ -1,38 +1,36 @@
Metadata-Version: 2.1
Name: pytest-mock
-Version: 3.12.0
+Version: 3.14.0
Summary: Thin-wrapper around the mock package for easier use with pytest
-Home-page: https://github.com/pytest-dev/pytest-mock/
-Author: Bruno Oliveira
-Author-email: [email protected]
+Author-email: Bruno Oliveira <[email protected]>
License: MIT
+Project-URL: Homepage, https://github.com/pytest-dev/pytest-mock/
Project-URL: Documentation, https://pytest-mock.readthedocs.io/en/latest/
Project-URL: Changelog, https://pytest-mock.readthedocs.io/en/latest/changelog.html
Project-URL: Source, https://github.com/pytest-dev/pytest-mock/
Project-URL: Tracker, https://github.com/pytest-dev/pytest-mock/issues
-Keywords: pytest mock
-Platform: any
+Keywords: pytest,mock
Classifier: Development Status :: 5 - Production/Stable
Classifier: Framework :: Pytest
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
-Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Software Development :: Testing
Requires-Python: >=3.8
Description-Content-Type: text/x-rst
License-File: LICENSE
-Requires-Dist: pytest >=5.0
+Requires-Dist: pytest >=6.2.5
Provides-Extra: dev
Requires-Dist: pre-commit ; extra == 'dev'
-Requires-Dist: tox ; extra == 'dev'
Requires-Dist: pytest-asyncio ; extra == 'dev'
+Requires-Dist: tox ; extra == 'dev'
===========
pytest-mock
diff --git a/contrib/python/pytest-mock/py3/pytest_mock/__init__.py b/contrib/python/pytest-mock/py3/pytest_mock/__init__.py
index 0afa47c0c17..75fd27afdeb 100644
--- a/contrib/python/pytest-mock/py3/pytest_mock/__init__.py
+++ b/contrib/python/pytest-mock/py3/pytest_mock/__init__.py
@@ -1,18 +1,22 @@
+from pytest_mock.plugin import AsyncMockType
+from pytest_mock.plugin import MockerFixture
+from pytest_mock.plugin import MockType
+from pytest_mock.plugin import PytestMockWarning
from pytest_mock.plugin import class_mocker
from pytest_mock.plugin import mocker
-from pytest_mock.plugin import MockerFixture
from pytest_mock.plugin import module_mocker
from pytest_mock.plugin import package_mocker
from pytest_mock.plugin import pytest_addoption
from pytest_mock.plugin import pytest_configure
-from pytest_mock.plugin import PytestMockWarning
from pytest_mock.plugin import session_mocker
MockFixture = MockerFixture # backward-compatibility only (#204)
__all__ = [
+ "AsyncMockType",
"MockerFixture",
"MockFixture",
+ "MockType",
"PytestMockWarning",
"pytest_addoption",
"pytest_configure",
diff --git a/contrib/python/pytest-mock/py3/pytest_mock/_version.py b/contrib/python/pytest-mock/py3/pytest_mock/_version.py
index 9e1a89958d8..431bd6d8195 100644
--- a/contrib/python/pytest-mock/py3/pytest_mock/_version.py
+++ b/contrib/python/pytest-mock/py3/pytest_mock/_version.py
@@ -12,5 +12,5 @@ __version__: str
__version_tuple__: VERSION_TUPLE
version_tuple: VERSION_TUPLE
-__version__ = version = '3.12.0'
-__version_tuple__ = version_tuple = (3, 12, 0)
+__version__ = version = '3.14.0'
+__version_tuple__ = version_tuple = (3, 14, 0)
diff --git a/contrib/python/pytest-mock/py3/pytest_mock/plugin.py b/contrib/python/pytest-mock/py3/pytest_mock/plugin.py
index 673f98bffb4..1e0a0b20255 100644
--- a/contrib/python/pytest-mock/py3/pytest_mock/plugin.py
+++ b/contrib/python/pytest-mock/py3/pytest_mock/plugin.py
@@ -2,23 +2,25 @@ import asyncio
import builtins
import functools
import inspect
-import sys
import unittest.mock
import warnings
+from dataclasses import dataclass
+from dataclasses import field
from typing import Any
from typing import Callable
-from typing import cast
from typing import Dict
from typing import Generator
from typing import Iterable
+from typing import Iterator
from typing import List
from typing import Mapping
from typing import Optional
-from typing import overload
from typing import Tuple
from typing import Type
from typing import TypeVar
from typing import Union
+from typing import cast
+from typing import overload
import pytest
@@ -27,22 +29,58 @@ from ._util import parse_ini_boolean
_T = TypeVar("_T")
-if sys.version_info >= (3, 8):
- AsyncMockType = unittest.mock.AsyncMock
- MockType = Union[
- unittest.mock.MagicMock,
- unittest.mock.AsyncMock,
- unittest.mock.NonCallableMagicMock,
- ]
-else:
- AsyncMockType = Any
- MockType = Union[unittest.mock.MagicMock, unittest.mock.NonCallableMagicMock]
+AsyncMockType = unittest.mock.AsyncMock
+MockType = Union[
+ unittest.mock.MagicMock,
+ unittest.mock.AsyncMock,
+ unittest.mock.NonCallableMagicMock,
+]
class PytestMockWarning(UserWarning):
"""Base class for all warnings emitted by pytest-mock."""
+@dataclass
+class MockCacheItem:
+ mock: MockType
+ patch: Optional[Any] = None
+
+
+@dataclass
+class MockCache:
+ """
+ Cache MagicMock and Patcher instances so we can undo them later.
+ """
+
+ cache: List[MockCacheItem] = field(default_factory=list)
+
+ def _find(self, mock: MockType) -> MockCacheItem:
+ for mock_item in self.cache:
+ if mock_item.mock is mock:
+ return mock_item
+ raise ValueError("This mock object is not registered")
+
+ def add(self, mock: MockType, **kwargs: Any) -> MockCacheItem:
+ self.cache.append(MockCacheItem(mock=mock, **kwargs))
+ return self.cache[-1]
+
+ def remove(self, mock: MockType) -> None:
+ mock_item = self._find(mock)
+ if mock_item.patch:
+ mock_item.patch.stop()
+ self.cache.remove(mock_item)
+
+ def clear(self) -> None:
+ for mock_item in reversed(self.cache):
+ if mock_item.patch is not None:
+ mock_item.patch.stop()
+ self.cache.clear()
+
+ def __iter__(self) -> Iterator[MockCacheItem]:
+ return iter(self.cache)
+
+
class MockerFixture:
"""
Fixture that provides the same interface to functions in the mock module,
@@ -50,11 +88,9 @@ class MockerFixture:
"""
def __init__(self, config: Any) -> None:
- self._patches_and_mocks: List[Tuple[Any, unittest.mock.MagicMock]] = []
+ self._mock_cache: MockCache = MockCache()
self.mock_module = mock_module = get_mock_module(config)
- self.patch = self._Patcher(
- self._patches_and_mocks, mock_module
- ) # type: MockerFixture._Patcher
+ self.patch = self._Patcher(self._mock_cache, mock_module) # type: MockerFixture._Patcher
# aliases for convenience
self.Mock = mock_module.Mock
self.MagicMock = mock_module.MagicMock
@@ -77,7 +113,7 @@ class MockerFixture:
m: MockType = self.mock_module.create_autospec(
spec, spec_set, instance, **kwargs
)
- self._patches_and_mocks.append((None, m))
+ self._mock_cache.add(m)
return m
def resetall(
@@ -95,37 +131,33 @@ class MockerFixture:
else:
supports_reset_mock_with_args = (self.Mock,)
- for p, m in self._patches_and_mocks:
+ for mock_item in self._mock_cache:
# See issue #237.
- if not hasattr(m, "reset_mock"):
+ if not hasattr(mock_item.mock, "reset_mock"):
continue
- if isinstance(m, supports_reset_mock_with_args):
- m.reset_mock(return_value=return_value, side_effect=side_effect)
+ # NOTE: The mock may be a dictionary
+ if hasattr(mock_item.mock, "spy_return_list"):
+ mock_item.mock.spy_return_list = []
+ if isinstance(mock_item.mock, supports_reset_mock_with_args):
+ mock_item.mock.reset_mock(
+ return_value=return_value, side_effect=side_effect
+ )
else:
- m.reset_mock()
+ mock_item.mock.reset_mock()
def stopall(self) -> None:
"""
Stop all patchers started by this fixture. Can be safely called multiple
times.
"""
- for p, m in reversed(self._patches_and_mocks):
- if p is not None:
- p.stop()
- self._patches_and_mocks.clear()
+ self._mock_cache.clear()
def stop(self, mock: unittest.mock.MagicMock) -> None:
"""
Stops a previous patch or spy call by passing the ``MagicMock`` object
returned by it.
"""
- for index, (p, m) in enumerate(self._patches_and_mocks):
- if mock is m:
- p.stop()
- del self._patches_and_mocks[index]
- break
- else:
- raise ValueError("This mock object is not registered")
+ self._mock_cache.remove(mock)
def spy(self, obj: object, name: str) -> MockType:
"""
@@ -137,14 +169,6 @@ class MockerFixture:
:return: Spy object.
"""
method = getattr(obj, name)
- if inspect.isclass(obj) and isinstance(
- inspect.getattr_static(obj, name), (classmethod, staticmethod)
- ):
- # Can't use autospec classmethod or staticmethod objects before 3.7
- # see: https://bugs.python.org/issue23078
- autospec = False
- else:
- autospec = inspect.ismethod(method) or inspect.isfunction(method)
def wrapper(*args, **kwargs):
spy_obj.spy_return = None
@@ -156,6 +180,7 @@ class MockerFixture:
raise
else:
spy_obj.spy_return = r
+ spy_obj.spy_return_list.append(r)
return r
async def async_wrapper(*args, **kwargs):
@@ -168,6 +193,7 @@ class MockerFixture:
raise
else:
spy_obj.spy_return = r
+ spy_obj.spy_return_list.append(r)
return r
if asyncio.iscoroutinefunction(method):
@@ -175,8 +201,11 @@ class MockerFixture:
else:
wrapped = functools.update_wrapper(wrapper, method)
+ autospec = inspect.ismethod(method) or inspect.isfunction(method)
+
spy_obj = self.patch.object(obj, name, side_effect=wrapped, autospec=autospec)
spy_obj.spy_return = None
+ spy_obj.spy_return_list = []
spy_obj.spy_exception = None
return spy_obj
@@ -214,8 +243,8 @@ class MockerFixture:
DEFAULT = object()
- def __init__(self, patches_and_mocks, mock_module):
- self.__patches_and_mocks = patches_and_mocks
+ def __init__(self, mock_cache, mock_module):
+ self.__mock_cache = mock_cache
self.mock_module = mock_module
def _start_patch(
@@ -227,22 +256,18 @@ class MockerFixture:
"""
p = mock_func(*args, **kwargs)
mocked: MockType = p.start()
- self.__patches_and_mocks.append((p, mocked))
+ self.__mock_cache.add(mock=mocked, patch=p)
if hasattr(mocked, "reset_mock"):
# check if `mocked` is actually a mock object, as depending on autospec or target
# parameters `mocked` can be anything
if hasattr(mocked, "__enter__") and warn_on_mock_enter:
- if sys.version_info >= (3, 8):
- depth = 5
- else:
- depth = 4
mocked.__enter__.side_effect = lambda: warnings.warn(
"Mocks returned by pytest-mock do not need to be used as context managers. "
"The mocker fixture automatically undoes mocking at the end of a test. "
"This warning can be ignored if it was triggered by mocking a context manager. "
"https://pytest-mock.readthedocs.io/en/latest/remarks.html#usage-as-context-manager",
PytestMockWarning,
- stacklevel=depth,
+ stacklevel=5,
)
return mocked
@@ -256,7 +281,7 @@ class MockerFixture:
spec_set: Optional[object] = None,
autospec: Optional[object] = None,
new_callable: object = None,
- **kwargs: Any
+ **kwargs: Any,
) -> MockType:
"""API to mock.patch.object"""
if new is self.DEFAULT:
@@ -272,7 +297,7 @@ class MockerFixture:
spec_set=spec_set,
autospec=autospec,
new_callable=new_callable,
- **kwargs
+ **kwargs,
)
def context_manager(
@@ -285,7 +310,7 @@ class MockerFixture:
spec_set: Optional[builtins.object] = None,
autospec: Optional[builtins.object] = None,
new_callable: builtins.object = None,
- **kwargs: Any
+ **kwargs: Any,
) -> MockType:
"""This is equivalent to mock.patch.object except that the returned mock
does not issue a warning when used as a context manager."""
@@ -302,7 +327,7 @@ class MockerFixture:
spec_set=spec_set,
autospec=autospec,
new_callable=new_callable,
- **kwargs
+ **kwargs,
)
def multiple(
@@ -313,7 +338,7 @@ class MockerFixture:
spec_set: Optional[builtins.object] = None,
autospec: Optional[builtins.object] = None,
new_callable: Optional[builtins.object] = None,
- **kwargs: Any
+ **kwargs: Any,
) -> Dict[str, MockType]:
"""API to mock.patch.multiple"""
return self._start_patch(
@@ -325,7 +350,7 @@ class MockerFixture:
spec_set=spec_set,
autospec=autospec,
new_callable=new_callable,
- **kwargs
+ **kwargs,
)
def dict(
@@ -333,7 +358,7 @@ class MockerFixture:
in_dict: Union[Mapping[Any, Any], str],
values: Union[Mapping[Any, Any], Iterable[Tuple[Any, Any]]] = (),
clear: bool = False,
- **kwargs: Any
+ **kwargs: Any,
) -> Any:
"""API to mock.patch.dict"""
return self._start_patch(
@@ -342,7 +367,7 @@ class MockerFixture:
in_dict,
values=values,
clear=clear,
- **kwargs
+ **kwargs,
)
@overload
@@ -355,9 +380,8 @@ class MockerFixture:
spec_set: Optional[builtins.object] = ...,
autospec: Optional[builtins.object] = ...,
new_callable: None = ...,
- **kwargs: Any
- ) -> MockType:
- ...
+ **kwargs: Any,
+ ) -> MockType: ...
@overload
def __call__(
@@ -369,9 +393,8 @@ class MockerFixture:
spec_set: Optional[builtins.object] = ...,
autospec: Optional[builtins.object] = ...,
new_callable: None = ...,
- **kwargs: Any
- ) -> _T:
- ...
+ **kwargs: Any,
+ ) -> _T: ...
@overload
def __call__(
@@ -383,9 +406,8 @@ class MockerFixture:
spec_set: Optional[builtins.object],
autospec: Optional[builtins.object],
new_callable: Callable[[], _T],
- **kwargs: Any
- ) -> _T:
- ...
+ **kwargs: Any,
+ ) -> _T: ...
@overload
def __call__(
@@ -398,9 +420,8 @@ class MockerFixture:
autospec: Optional[builtins.object] = ...,
*,
new_callable: Callable[[], _T],
- **kwargs: Any
- ) -> _T:
- ...
+ **kwargs: Any,
+ ) -> _T: ...
def __call__(
self,
@@ -411,7 +432,7 @@ class MockerFixture:
spec_set: Optional[builtins.object] = None,
autospec: Optional[builtins.object] = None,
new_callable: Optional[Callable[[], Any]] = None,
- **kwargs: Any
+ **kwargs: Any,
) -> Any:
"""API to mock.patch"""
if new is self.DEFAULT:
@@ -426,7 +447,7 @@ class MockerFixture:
spec_set=spec_set,
autospec=autospec,
new_callable=new_callable,
- **kwargs
+ **kwargs,
)
diff --git a/contrib/python/pytest-mock/py3/ya.make b/contrib/python/pytest-mock/py3/ya.make
index 40f864e45da..7b9bcc9b14f 100644
--- a/contrib/python/pytest-mock/py3/ya.make
+++ b/contrib/python/pytest-mock/py3/ya.make
@@ -2,7 +2,7 @@
PY3_LIBRARY()
-VERSION(3.12.0)
+VERSION(3.14.0)
LICENSE(MIT)
diff --git a/contrib/python/requests-oauthlib/.dist-info/METADATA b/contrib/python/requests-oauthlib/.dist-info/METADATA
index 1029e2ff65a..0a62eb5b6c3 100644
--- a/contrib/python/requests-oauthlib/.dist-info/METADATA
+++ b/contrib/python/requests-oauthlib/.dist-info/METADATA
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: requests-oauthlib
-Version: 1.4.0
+Version: 2.0.0
Summary: OAuthlib authentication support for Requests.
Home-page: https://github.com/requests/requests-oauthlib
Author: Kenneth Reitz
@@ -11,8 +11,6 @@ Classifier: Intended Audience :: Developers
Classifier: Natural Language :: English
Classifier: License :: OSI Approved :: BSD License
Classifier: Programming Language :: Python
-Classifier: Programming Language :: Python :: 2
-Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
@@ -25,7 +23,7 @@ Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
-Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
+Requires-Python: >=3.4
Description-Content-Type: text/x-rst
License-File: LICENSE
Requires-Dist: oauthlib >=3.0.0
@@ -96,7 +94,7 @@ To install requests and requests_oauthlib you can use pip:
History
-------
-v1.4.0 (27 Feb 2024)
+v2.0.0 (22 March 2024)
++++++++++++++++++++++++
Full set of changes are in [github](https://github.com/requests/requests-oauthlib/milestone/4?closed=1).
@@ -117,6 +115,11 @@ Additions & changes:
- Updated dependencies
- Cleanup some docs and examples
+v1.4.0 (27 Feb 2024)
+++++++++++++++++++++++++
+
+- Version 2.0.0 published initially as 1.4.0, it was yanked eventually.
+
v1.3.1 (21 January 2022)
++++++++++++++++++++++++
diff --git a/contrib/python/requests-oauthlib/requests_oauthlib/__init__.py b/contrib/python/requests-oauthlib/requests_oauthlib/__init__.py
index a71064cca08..865d72fb768 100644
--- a/contrib/python/requests-oauthlib/requests_oauthlib/__init__.py
+++ b/contrib/python/requests-oauthlib/requests_oauthlib/__init__.py
@@ -6,7 +6,7 @@ from .oauth1_session import OAuth1Session
from .oauth2_auth import OAuth2
from .oauth2_session import OAuth2Session, TokenUpdated
-__version__ = "1.4.0"
+__version__ = "2.0.0"
import requests
diff --git a/contrib/python/requests-oauthlib/tests/examples/__init__.py b/contrib/python/requests-oauthlib/tests/examples/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/contrib/python/requests-oauthlib/tests/examples/__init__.py
diff --git a/contrib/python/requests-oauthlib/ya.make b/contrib/python/requests-oauthlib/ya.make
index d1a60dc1009..da3d8536ed6 100644
--- a/contrib/python/requests-oauthlib/ya.make
+++ b/contrib/python/requests-oauthlib/ya.make
@@ -2,7 +2,7 @@
PY3_LIBRARY()
-VERSION(1.4.0)
+VERSION(2.0.0)
LICENSE(ISC)
diff --git a/contrib/python/ruamel.yaml.clib/py2/.dist-info/METADATA b/contrib/python/ruamel.yaml.clib/py2/.dist-info/METADATA
index 6c952ac9d6b..df2e2797e24 100644
--- a/contrib/python/ruamel.yaml.clib/py2/.dist-info/METADATA
+++ b/contrib/python/ruamel.yaml.clib/py2/.dist-info/METADATA
@@ -22,7 +22,6 @@ Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Description-Content-Type: text/x-rst
-
ruamel.yaml.clib
================
diff --git a/contrib/python/simplejson/py2/.dist-info/METADATA b/contrib/python/simplejson/py2/.dist-info/METADATA
index 030823c2a19..498829fb7e7 100644
--- a/contrib/python/simplejson/py2/.dist-info/METADATA
+++ b/contrib/python/simplejson/py2/.dist-info/METADATA
@@ -30,7 +30,6 @@ Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=2.5, !=3.0.*, !=3.1.*, !=3.2.*
-License-File: LICENSE.txt
simplejson
----------
@@ -66,3 +65,5 @@ Python 2.2. This is based on a very old version of simplejson,
is not maintained, and should only be used as a last resort.
.. _python2.2: https://github.com/simplejson/simplejson/tree/python2.2
+
+
diff --git a/contrib/python/tzlocal/py2/.dist-info/METADATA b/contrib/python/tzlocal/py2/.dist-info/METADATA
deleted file mode 100644
index 7bafd427d5c..00000000000
--- a/contrib/python/tzlocal/py2/.dist-info/METADATA
+++ /dev/null
@@ -1,326 +0,0 @@
-Metadata-Version: 2.1
-Name: tzlocal
-Version: 2.1
-Summary: tzinfo object for the local timezone
-Home-page: https://github.com/regebro/tzlocal
-Author: Lennart Regebro
-Author-email: [email protected]
-License: MIT
-Keywords: timezone pytz
-Platform: UNKNOWN
-Classifier: Development Status :: 5 - Production/Stable
-Classifier: License :: OSI Approved :: MIT License
-Classifier: Operating System :: Microsoft :: Windows
-Classifier: Operating System :: Unix
-Classifier: Operating System :: MacOS :: MacOS X
-Classifier: Programming Language :: Python :: 2.7
-Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.6
-Classifier: Programming Language :: Python :: 3.7
-Classifier: Programming Language :: Python :: 3.8
-Requires-Dist: pytz
-
-tzlocal
-=======
-
-This Python module returns a ``tzinfo`` object with the local timezone information under Unix and Win-32.
-It requires ``pytz``, and returns ``pytz`` ``tzinfo`` objects.
-
-This module attempts to fix a glaring hole in ``pytz``, that there is no way to
-get the local timezone information, unless you know the zoneinfo name, and
-under several Linux distros that's hard or impossible to figure out.
-
-Also, with Windows different timezone system using pytz isn't of much use
-unless you separately configure the zoneinfo timezone name.
-
-With ``tzlocal`` you only need to call ``get_localzone()`` and you will get a
-``tzinfo`` object with the local time zone info. On some Unices you will still
-not get to know what the timezone name is, but you don't need that when you
-have the tzinfo file. However, if the timezone name is readily available it
-will be used.
-
-
-Supported systems
------------------
-
-These are the systems that are in theory supported:
-
- * Windows 2000 and later
-
- * Any unix-like system with a ``/etc/localtime`` or ``/usr/local/etc/localtime``
-
-If you have one of the above systems and it does not work, it's a bug.
-Please report it.
-
-Please note that if you getting a time zone called ``local``, this is not a bug, it's
-actually the main feature of ``tzlocal``, that even if your system does NOT have a configuration file
-with the zoneinfo name of your time zone, it will still work.
-
-You can also use ``tzlocal`` to get the name of your local timezone, but only if your system is
-configured to make that possible. ``tzlocal`` looks for the timezone name in ``/etc/timezone``, ``/var/db/zoneinfo``,
-``/etc/sysconfig/clock`` and ``/etc/conf.d/clock``. If your ``/etc/localtime`` is a symlink it can also extract the
-name from that symlink.
-
-If you need the name of your local time zone, then please make sure your system is properly configured to allow that.
-If it isn't configured, tzlocal will default to UTC.
-
-Usage
------
-
-Load the local timezone:
-
- >>> from tzlocal import get_localzone
- >>> tz = get_localzone()
- >>> tz
- <DstTzInfo 'Europe/Warsaw' WMT+1:24:00 STD>
-
-Create a local datetime:
-
- >>> from datetime import datetime
- >>> dt = tz.localize(datetime(2015, 4, 10, 7, 22))
- >>> dt
- datetime.datetime(2015, 4, 10, 7, 22, tzinfo=<DstTzInfo 'Europe/Warsaw' CEST+2:00:00 DST>)
-
-Lookup another timezone with `pytz`:
-
- >>> import pytz
- >>> eastern = pytz.timezone('US/Eastern')
-
-Convert the datetime:
-
- >>> dt.astimezone(eastern)
- datetime.datetime(2015, 4, 10, 1, 22, tzinfo=<DstTzInfo 'US/Eastern' EDT-1 day, 20:00:00 DST>)
-
-
-Maintainer
-----------
-
-* Lennart Regebro, [email protected]
-
-Contributors
-------------
-
-* Marc Van Olmen
-* Benjamen Meyer
-* Manuel Ebert
-* Xiaokun Zhu
-* Cameris
-* Edward Betts
-* McK KIM
-* Cris Ewing
-* Ayala Shachar
-* Lev Maximov
-* Jakub Wilk
-* John Quarles
-* Preston Landers
-* Victor Torres
-* Jean Jordaan
-* Zackary Welch
-* Mickaël Schoentgen
-* Gabriel Corona
-
-(Sorry if I forgot someone)
-
-License
--------
-
-* MIT https://opensource.org/licenses/MIT
-
-
-Changes
-=======
-
-2.1 (2020-05-08)
-----------------
-
-- No changes.
-
-
-2.1b1 (2020-02-08)
-------------------
-
-- The is_dst flag is wrong for Europe/Dublin on some Unix releases.
- I changed to another way of determining if DST is in effect or not.
-
-- Added support for Python 3.7 and 3.8. Dropped 3.5 although it still works.
-
-
-2.0.0 (2019-07-23)
-------------------
-
-- No differences since 2.0.0b3
-
-Major differences since 1.5.1
-.............................
-
-- When no time zone configuration can be find, tzlocal now return UTC.
- This is a major difference from 1.x, where an exception would be raised.
- This change is because Docker images often have no configuration at all,
- and the unix utilities will then default to UTC, so we follow that.
-
-- If tzlocal on Unix finds a timezone name in a /etc config file, then
- tzlocal now verifies that the timezone it fouds has the same offset as
- the local computer is configured with. If it doesn't, something is
- configured incorrectly. (Victor Torres, regebro)
-
-- Get timezone via Termux `getprop` wrapper on Android. It's not officially
- supported because we can't test it, but at least we make an effort.
- (Jean Jordaan)
-
-Minor differences and bug fixes
-...............................
-
-- Skip comment lines when parsing /etc/timezone. (Edward Betts)
-
-- Don't load timezone from current directory. (Gabriel Corona)
-
-- Now verifies that the config files actually contain something before
- reading them. (Zackary Welch, regebro)
-
-- Got rid of a BytesWarning (Mickaël Schoentgen)
-
-- Now handles if config file paths exists, but are directories.
-
-- Moved tests out from distributions
-
-- Support wheels
-
-
-1.5.1 (2017-12-01)
-------------------
-
-- 1.5 had a bug that slipped through testing, fixed that,
- increased test coverage.
-
-
-1.5 (2017-11-30)
-----------------
-
-- No longer treats macOS as special, but as a unix.
-
-- get_windows_info.py is renamed to update_windows_mappings.py
-
-- Windows mappings now also contain mappings from deprecated zoneinfo names.
- (Preston-Landers, regebro)
-
-
-1.4 (2017-04-18)
-----------------
-
-- I use MIT on my other projects, so relicensing.
-
-
-1.4b1 (2017-04-14)
-------------------
-
-- Dropping support for Python versions nobody uses (2.5, 3.1, 3.2), adding 3.6
- Python 3.1 and 3.2 still works, 2.5 has been broken for some time.
-
-- Ayalash's OS X fix didn't work on Python 2.7, fixed that.
-
-
-1.3.2 (2017-04-12)
-------------------
-
-- Ensure closing of subprocess on OS X (ayalash)
-
-- Removed unused imports (jwilk)
-
-- Closes stdout and stderr to get rid of ResourceWarnings (johnwquarles)
-
-- Updated Windows timezones (axil)
-
-
-1.3 (2016-10-15)
-----------------
-
-- #34: Added support for /var/db/zoneinfo
-
-
-1.2.2 (2016-03-02)
-------------------
-
-- #30: Fixed a bug on OS X.
-
-
-1.2.1 (2016-02-28)
-------------------
-
-- Tests failed if TZ was set in the environment. (EdwardBetts)
-
-- Replaces os.popen() with subprocess.Popen() for OS X to
- handle when systemsetup doesn't exist. (mckabi, cewing)
-
-
-1.2 (2015-06-14)
-----------------
-
-- Systemd stores no time zone name, forcing us to look at the name of the file
- that localtime symlinks to. (cameris)
-
-
-1.1.2 (2014-10-18)
-------------------
-
-- Timezones that has 3 items did not work on Mac OS X.
- (Marc Van Olmen)
-
-- Now doesn't fail if the TZ environment variable isn't an Olsen time zone.
-
-- Some timezones on Windows can apparently be empty (perhaps the are deleted).
- Now these are ignored.
- (Xiaokun Zhu)
-
-
-1.1.1 (2014-01-29)
-------------------
-
-- I forgot to add Etc/UTC as an alias for Etc/GMT.
-
-
-1.1 (2014-01-28)
-----------------
-
-- Adding better support for OS X.
-
-- Added support to map from tzdata/Olsen names to Windows names.
- (Thanks to Benjamen Meyer).
-
-
-1.0 (2013-05-29)
-----------------
-
-- Fixed some more cases where spaces needs replacing with underscores.
-
-- Better handling of misconfigured /etc/timezone.
-
-- Better error message on Windows if we can't find a timezone at all.
-
-
-0.3 (2012-09-13)
-----------------
-
-- Windows 7 support.
-
-- Python 2.5 supported; because it only needed a __future__ import.
-
-- Python 3.3 tested, it worked.
-
-- Got rid of relative imports, because I don't actually like them,
- so I don't know why I used them in the first place.
-
-- For each Windows zone, use the default zoneinfo zone, not the last one.
-
-
-0.2 (2012-09-12)
-----------------
-
-- Python 3 support.
-
-
-0.1 (2012-09-11)
-----------------
-
-- Initial release.
-
-
diff --git a/contrib/python/tzlocal/py2/.dist-info/top_level.txt b/contrib/python/tzlocal/py2/.dist-info/top_level.txt
deleted file mode 100644
index cd5e9b12a4b..00000000000
--- a/contrib/python/tzlocal/py2/.dist-info/top_level.txt
+++ /dev/null
@@ -1 +0,0 @@
-tzlocal
diff --git a/contrib/python/tzlocal/py2/LICENSE.txt b/contrib/python/tzlocal/py2/LICENSE.txt
deleted file mode 100644
index 9be1d2fe595..00000000000
--- a/contrib/python/tzlocal/py2/LICENSE.txt
+++ /dev/null
@@ -1,19 +0,0 @@
-Copyright 2011-2017 Lennart Regebro
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/contrib/python/tzlocal/py2/README.rst b/contrib/python/tzlocal/py2/README.rst
deleted file mode 100644
index eaa0bdd8132..00000000000
--- a/contrib/python/tzlocal/py2/README.rst
+++ /dev/null
@@ -1,105 +0,0 @@
-tzlocal
-=======
-
-This Python module returns a ``tzinfo`` object with the local timezone information under Unix and Win-32.
-It requires ``pytz``, and returns ``pytz`` ``tzinfo`` objects.
-
-This module attempts to fix a glaring hole in ``pytz``, that there is no way to
-get the local timezone information, unless you know the zoneinfo name, and
-under several Linux distros that's hard or impossible to figure out.
-
-Also, with Windows different timezone system using pytz isn't of much use
-unless you separately configure the zoneinfo timezone name.
-
-With ``tzlocal`` you only need to call ``get_localzone()`` and you will get a
-``tzinfo`` object with the local time zone info. On some Unices you will still
-not get to know what the timezone name is, but you don't need that when you
-have the tzinfo file. However, if the timezone name is readily available it
-will be used.
-
-
-Supported systems
------------------
-
-These are the systems that are in theory supported:
-
- * Windows 2000 and later
-
- * Any unix-like system with a ``/etc/localtime`` or ``/usr/local/etc/localtime``
-
-If you have one of the above systems and it does not work, it's a bug.
-Please report it.
-
-Please note that if you getting a time zone called ``local``, this is not a bug, it's
-actually the main feature of ``tzlocal``, that even if your system does NOT have a configuration file
-with the zoneinfo name of your time zone, it will still work.
-
-You can also use ``tzlocal`` to get the name of your local timezone, but only if your system is
-configured to make that possible. ``tzlocal`` looks for the timezone name in ``/etc/timezone``, ``/var/db/zoneinfo``,
-``/etc/sysconfig/clock`` and ``/etc/conf.d/clock``. If your ``/etc/localtime`` is a symlink it can also extract the
-name from that symlink.
-
-If you need the name of your local time zone, then please make sure your system is properly configured to allow that.
-If it isn't configured, tzlocal will default to UTC.
-
-Usage
------
-
-Load the local timezone:
-
- >>> from tzlocal import get_localzone
- >>> tz = get_localzone()
- >>> tz
- <DstTzInfo 'Europe/Warsaw' WMT+1:24:00 STD>
-
-Create a local datetime:
-
- >>> from datetime import datetime
- >>> dt = tz.localize(datetime(2015, 4, 10, 7, 22))
- >>> dt
- datetime.datetime(2015, 4, 10, 7, 22, tzinfo=<DstTzInfo 'Europe/Warsaw' CEST+2:00:00 DST>)
-
-Lookup another timezone with `pytz`:
-
- >>> import pytz
- >>> eastern = pytz.timezone('US/Eastern')
-
-Convert the datetime:
-
- >>> dt.astimezone(eastern)
- datetime.datetime(2015, 4, 10, 1, 22, tzinfo=<DstTzInfo 'US/Eastern' EDT-1 day, 20:00:00 DST>)
-
-
-Maintainer
-----------
-
-* Lennart Regebro, [email protected]
-
-Contributors
-------------
-
-* Marc Van Olmen
-* Benjamen Meyer
-* Manuel Ebert
-* Xiaokun Zhu
-* Cameris
-* Edward Betts
-* McK KIM
-* Cris Ewing
-* Ayala Shachar
-* Lev Maximov
-* Jakub Wilk
-* John Quarles
-* Preston Landers
-* Victor Torres
-* Jean Jordaan
-* Zackary Welch
-* Mickaël Schoentgen
-* Gabriel Corona
-
-(Sorry if I forgot someone)
-
-License
--------
-
-* MIT https://opensource.org/licenses/MIT
diff --git a/contrib/python/tzlocal/py2/tzlocal/__init__.py b/contrib/python/tzlocal/py2/tzlocal/__init__.py
deleted file mode 100644
index c8196d66d9e..00000000000
--- a/contrib/python/tzlocal/py2/tzlocal/__init__.py
+++ /dev/null
@@ -1,5 +0,0 @@
-import sys
-if sys.platform == 'win32':
- from tzlocal.win32 import get_localzone, reload_localzone
-else:
- from tzlocal.unix import get_localzone, reload_localzone
diff --git a/contrib/python/tzlocal/py2/tzlocal/unix.py b/contrib/python/tzlocal/py2/tzlocal/unix.py
deleted file mode 100644
index 8574965a5a9..00000000000
--- a/contrib/python/tzlocal/py2/tzlocal/unix.py
+++ /dev/null
@@ -1,174 +0,0 @@
-import os
-import pytz
-import re
-import warnings
-
-from tzlocal import utils
-
-_cache_tz = None
-
-
-def _tz_from_env(tzenv):
- if tzenv[0] == ':':
- tzenv = tzenv[1:]
-
- # TZ specifies a file
- if os.path.isabs(tzenv) and os.path.exists(tzenv):
- with open(tzenv, 'rb') as tzfile:
- return pytz.tzfile.build_tzinfo('local', tzfile)
-
- # TZ specifies a zoneinfo zone.
- try:
- tz = pytz.timezone(tzenv)
- # That worked, so we return this:
- return tz
- except pytz.UnknownTimeZoneError:
- raise pytz.UnknownTimeZoneError(
- "tzlocal() does not support non-zoneinfo timezones like %s. \n"
- "Please use a timezone in the form of Continent/City")
-
-
-def _try_tz_from_env():
- tzenv = os.environ.get('TZ')
- if tzenv:
- try:
- return _tz_from_env(tzenv)
- except pytz.UnknownTimeZoneError:
- pass
-
-
-def _get_localzone(_root='/'):
- """Tries to find the local timezone configuration.
-
- This method prefers finding the timezone name and passing that to pytz,
- over passing in the localtime file, as in the later case the zoneinfo
- name is unknown.
-
- The parameter _root makes the function look for files like /etc/localtime
- beneath the _root directory. This is primarily used by the tests.
- In normal usage you call the function without parameters."""
-
- tzenv = _try_tz_from_env()
- if tzenv:
- return tzenv
-
- # Are we under Termux on Android?
- if os.path.exists('/system/bin/getprop'):
- import subprocess
- androidtz = subprocess.check_output(['getprop', 'persist.sys.timezone']).strip().decode()
- return pytz.timezone(androidtz)
-
- # Now look for distribution specific configuration files
- # that contain the timezone name.
- for configfile in ('etc/timezone', 'var/db/zoneinfo'):
- tzpath = os.path.join(_root, configfile)
- try:
- with open(tzpath, 'rb') as tzfile:
- data = tzfile.read()
-
- # Issue #3 was that /etc/timezone was a zoneinfo file.
- # That's a misconfiguration, but we need to handle it gracefully:
- if data[:5] == b'TZif2':
- continue
-
- etctz = data.strip().decode()
- if not etctz:
- # Empty file, skip
- continue
- for etctz in data.decode().splitlines():
- # Get rid of host definitions and comments:
- if ' ' in etctz:
- etctz, dummy = etctz.split(' ', 1)
- if '#' in etctz:
- etctz, dummy = etctz.split('#', 1)
- if not etctz:
- continue
- tz = pytz.timezone(etctz.replace(' ', '_'))
- if _root == '/':
- # We are using a file in etc to name the timezone.
- # Verify that the timezone specified there is actually used:
- utils.assert_tz_offset(tz)
- return tz
-
- except IOError:
- # File doesn't exist or is a directory
- continue
-
- # CentOS has a ZONE setting in /etc/sysconfig/clock,
- # OpenSUSE has a TIMEZONE setting in /etc/sysconfig/clock and
- # Gentoo has a TIMEZONE setting in /etc/conf.d/clock
- # We look through these files for a timezone:
-
- zone_re = re.compile(r'\s*ZONE\s*=\s*\"')
- timezone_re = re.compile(r'\s*TIMEZONE\s*=\s*\"')
- end_re = re.compile('\"')
-
- for filename in ('etc/sysconfig/clock', 'etc/conf.d/clock'):
- tzpath = os.path.join(_root, filename)
- try:
- with open(tzpath, 'rt') as tzfile:
- data = tzfile.readlines()
-
- for line in data:
- # Look for the ZONE= setting.
- match = zone_re.match(line)
- if match is None:
- # No ZONE= setting. Look for the TIMEZONE= setting.
- match = timezone_re.match(line)
- if match is not None:
- # Some setting existed
- line = line[match.end():]
- etctz = line[:end_re.search(line).start()]
-
- # We found a timezone
- tz = pytz.timezone(etctz.replace(' ', '_'))
- if _root == '/':
- # We are using a file in etc to name the timezone.
- # Verify that the timezone specified there is actually used:
- utils.assert_tz_offset(tz)
- return tz
-
- except IOError:
- # File doesn't exist or is a directory
- continue
-
- # systemd distributions use symlinks that include the zone name,
- # see manpage of localtime(5) and timedatectl(1)
- tzpath = os.path.join(_root, 'etc/localtime')
- if os.path.exists(tzpath) and os.path.islink(tzpath):
- tzpath = os.path.realpath(tzpath)
- start = tzpath.find("/")+1
- while start != 0:
- tzpath = tzpath[start:]
- try:
- return pytz.timezone(tzpath)
- except pytz.UnknownTimeZoneError:
- pass
- start = tzpath.find("/")+1
-
- # No explicit setting existed. Use localtime
- for filename in ('etc/localtime', 'usr/local/etc/localtime'):
- tzpath = os.path.join(_root, filename)
-
- if not os.path.exists(tzpath):
- continue
- with open(tzpath, 'rb') as tzfile:
- return pytz.tzfile.build_tzinfo('local', tzfile)
-
- warnings.warn('Can not find any timezone configuration, defaulting to UTC.')
- return pytz.utc
-
-def get_localzone():
- """Get the computers configured local timezone, if any."""
- global _cache_tz
- if _cache_tz is None:
- _cache_tz = _get_localzone()
-
- return _cache_tz
-
-
-def reload_localzone():
- """Reload the cached localzone. You need to call this if the timezone has changed."""
- global _cache_tz
- _cache_tz = _get_localzone()
- return _cache_tz
diff --git a/contrib/python/tzlocal/py2/tzlocal/utils.py b/contrib/python/tzlocal/py2/tzlocal/utils.py
deleted file mode 100644
index 5a6779903de..00000000000
--- a/contrib/python/tzlocal/py2/tzlocal/utils.py
+++ /dev/null
@@ -1,46 +0,0 @@
-# -*- coding: utf-8 -*-
-import time
-import datetime
-import calendar
-
-
-def get_system_offset():
- """Get system's timezone offset using built-in library time.
-
- For the Timezone constants (altzone, daylight, timezone, and tzname), the
- value is determined by the timezone rules in effect at module load time or
- the last time tzset() is called and may be incorrect for times in the past.
-
- To keep compatibility with Windows, we're always importing time module here.
- """
-
- localtime = calendar.timegm(time.localtime())
- gmtime = calendar.timegm(time.gmtime())
- offset = gmtime - localtime
- # We could get the localtime and gmtime on either side of a second switch
- # so we check that the difference is less than one minute, because nobody
- # has that small DST differences.
- if abs(offset - time.altzone) < 60:
- return -time.altzone
- else:
- return -time.timezone
-
-
-def get_tz_offset(tz):
- """Get timezone's offset using built-in function datetime.utcoffset()."""
- return int(datetime.datetime.now(tz).utcoffset().total_seconds())
-
-
-def assert_tz_offset(tz):
- """Assert that system's timezone offset equals to the timezone offset found.
-
- If they don't match, we probably have a misconfiguration, for example, an
- incorrect timezone set in /etc/timezone file in systemd distributions."""
- tz_offset = get_tz_offset(tz)
- system_offset = get_system_offset()
- if tz_offset != system_offset:
- msg = ('Timezone offset does not match system offset: {0} != {1}. '
- 'Please, check your config files.').format(
- tz_offset, system_offset
- )
- raise ValueError(msg)
diff --git a/contrib/python/tzlocal/py2/tzlocal/win32.py b/contrib/python/tzlocal/py2/tzlocal/win32.py
deleted file mode 100644
index fcc42a23f3f..00000000000
--- a/contrib/python/tzlocal/py2/tzlocal/win32.py
+++ /dev/null
@@ -1,104 +0,0 @@
-try:
- import _winreg as winreg
-except ImportError:
- import winreg
-
-import pytz
-
-from tzlocal.windows_tz import win_tz
-from tzlocal import utils
-
-_cache_tz = None
-
-
-def valuestodict(key):
- """Convert a registry key's values to a dictionary."""
- dict = {}
- size = winreg.QueryInfoKey(key)[1]
- for i in range(size):
- data = winreg.EnumValue(key, i)
- dict[data[0]] = data[1]
- return dict
-
-
-def get_localzone_name():
- # Windows is special. It has unique time zone names (in several
- # meanings of the word) available, but unfortunately, they can be
- # translated to the language of the operating system, so we need to
- # do a backwards lookup, by going through all time zones and see which
- # one matches.
- handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
-
- TZLOCALKEYNAME = r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation"
- localtz = winreg.OpenKey(handle, TZLOCALKEYNAME)
- keyvalues = valuestodict(localtz)
- localtz.Close()
-
- if 'TimeZoneKeyName' in keyvalues:
- # Windows 7 (and Vista?)
-
- # For some reason this returns a string with loads of NUL bytes at
- # least on some systems. I don't know if this is a bug somewhere, I
- # just work around it.
- tzkeyname = keyvalues['TimeZoneKeyName'].split('\x00', 1)[0]
- else:
- # Windows 2000 or XP
-
- # This is the localized name:
- tzwin = keyvalues['StandardName']
-
- # Open the list of timezones to look up the real name:
- TZKEYNAME = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones"
- tzkey = winreg.OpenKey(handle, TZKEYNAME)
-
- # Now, match this value to Time Zone information
- tzkeyname = None
- for i in range(winreg.QueryInfoKey(tzkey)[0]):
- subkey = winreg.EnumKey(tzkey, i)
- sub = winreg.OpenKey(tzkey, subkey)
- data = valuestodict(sub)
- sub.Close()
- try:
- if data['Std'] == tzwin:
- tzkeyname = subkey
- break
- except KeyError:
- # This timezone didn't have proper configuration.
- # Ignore it.
- pass
-
- tzkey.Close()
- handle.Close()
-
- if tzkeyname is None:
- raise LookupError('Can not find Windows timezone configuration')
-
- timezone = win_tz.get(tzkeyname)
- if timezone is None:
- # Nope, that didn't work. Try adding "Standard Time",
- # it seems to work a lot of times:
- timezone = win_tz.get(tzkeyname + " Standard Time")
-
- # Return what we have.
- if timezone is None:
- raise pytz.UnknownTimeZoneError('Can not find timezone ' + tzkeyname)
-
- return timezone
-
-
-def get_localzone():
- """Returns the zoneinfo-based tzinfo object that matches the Windows-configured timezone."""
- global _cache_tz
- if _cache_tz is None:
- _cache_tz = pytz.timezone(get_localzone_name())
-
- utils.assert_tz_offset(_cache_tz)
- return _cache_tz
-
-
-def reload_localzone():
- """Reload the cached localzone. You need to call this if the timezone has changed."""
- global _cache_tz
- _cache_tz = pytz.timezone(get_localzone_name())
- utils.assert_tz_offset(_cache_tz)
- return _cache_tz
diff --git a/contrib/python/tzlocal/py2/tzlocal/windows_tz.py b/contrib/python/tzlocal/py2/tzlocal/windows_tz.py
deleted file mode 100644
index 86ba807d060..00000000000
--- a/contrib/python/tzlocal/py2/tzlocal/windows_tz.py
+++ /dev/null
@@ -1,697 +0,0 @@
-# This file is autogenerated by the update_windows_mapping.py script
-# Do not edit.
-win_tz = {'AUS Central Standard Time': 'Australia/Darwin',
- 'AUS Eastern Standard Time': 'Australia/Sydney',
- 'Afghanistan Standard Time': 'Asia/Kabul',
- 'Alaskan Standard Time': 'America/Anchorage',
- 'Aleutian Standard Time': 'America/Adak',
- 'Altai Standard Time': 'Asia/Barnaul',
- 'Arab Standard Time': 'Asia/Riyadh',
- 'Arabian Standard Time': 'Asia/Dubai',
- 'Arabic Standard Time': 'Asia/Baghdad',
- 'Argentina Standard Time': 'America/Buenos_Aires',
- 'Astrakhan Standard Time': 'Europe/Astrakhan',
- 'Atlantic Standard Time': 'America/Halifax',
- 'Aus Central W. Standard Time': 'Australia/Eucla',
- 'Azerbaijan Standard Time': 'Asia/Baku',
- 'Azores Standard Time': 'Atlantic/Azores',
- 'Bahia Standard Time': 'America/Bahia',
- 'Bangladesh Standard Time': 'Asia/Dhaka',
- 'Belarus Standard Time': 'Europe/Minsk',
- 'Bougainville Standard Time': 'Pacific/Bougainville',
- 'Canada Central Standard Time': 'America/Regina',
- 'Cape Verde Standard Time': 'Atlantic/Cape_Verde',
- 'Caucasus Standard Time': 'Asia/Yerevan',
- 'Cen. Australia Standard Time': 'Australia/Adelaide',
- 'Central America Standard Time': 'America/Guatemala',
- 'Central Asia Standard Time': 'Asia/Almaty',
- 'Central Brazilian Standard Time': 'America/Cuiaba',
- 'Central Europe Standard Time': 'Europe/Budapest',
- 'Central European Standard Time': 'Europe/Warsaw',
- 'Central Pacific Standard Time': 'Pacific/Guadalcanal',
- 'Central Standard Time': 'America/Chicago',
- 'Central Standard Time (Mexico)': 'America/Mexico_City',
- 'Chatham Islands Standard Time': 'Pacific/Chatham',
- 'China Standard Time': 'Asia/Shanghai',
- 'Cuba Standard Time': 'America/Havana',
- 'Dateline Standard Time': 'Etc/GMT+12',
- 'E. Africa Standard Time': 'Africa/Nairobi',
- 'E. Australia Standard Time': 'Australia/Brisbane',
- 'E. Europe Standard Time': 'Europe/Chisinau',
- 'E. South America Standard Time': 'America/Sao_Paulo',
- 'Easter Island Standard Time': 'Pacific/Easter',
- 'Eastern Standard Time': 'America/New_York',
- 'Eastern Standard Time (Mexico)': 'America/Cancun',
- 'Egypt Standard Time': 'Africa/Cairo',
- 'Ekaterinburg Standard Time': 'Asia/Yekaterinburg',
- 'FLE Standard Time': 'Europe/Kiev',
- 'Fiji Standard Time': 'Pacific/Fiji',
- 'GMT Standard Time': 'Europe/London',
- 'GTB Standard Time': 'Europe/Bucharest',
- 'Georgian Standard Time': 'Asia/Tbilisi',
- 'Greenland Standard Time': 'America/Godthab',
- 'Greenwich Standard Time': 'Atlantic/Reykjavik',
- 'Haiti Standard Time': 'America/Port-au-Prince',
- 'Hawaiian Standard Time': 'Pacific/Honolulu',
- 'India Standard Time': 'Asia/Calcutta',
- 'Iran Standard Time': 'Asia/Tehran',
- 'Israel Standard Time': 'Asia/Jerusalem',
- 'Jordan Standard Time': 'Asia/Amman',
- 'Kaliningrad Standard Time': 'Europe/Kaliningrad',
- 'Korea Standard Time': 'Asia/Seoul',
- 'Libya Standard Time': 'Africa/Tripoli',
- 'Line Islands Standard Time': 'Pacific/Kiritimati',
- 'Lord Howe Standard Time': 'Australia/Lord_Howe',
- 'Magadan Standard Time': 'Asia/Magadan',
- 'Magallanes Standard Time': 'America/Punta_Arenas',
- 'Marquesas Standard Time': 'Pacific/Marquesas',
- 'Mauritius Standard Time': 'Indian/Mauritius',
- 'Middle East Standard Time': 'Asia/Beirut',
- 'Montevideo Standard Time': 'America/Montevideo',
- 'Morocco Standard Time': 'Africa/Casablanca',
- 'Mountain Standard Time': 'America/Denver',
- 'Mountain Standard Time (Mexico)': 'America/Chihuahua',
- 'Myanmar Standard Time': 'Asia/Rangoon',
- 'N. Central Asia Standard Time': 'Asia/Novosibirsk',
- 'Namibia Standard Time': 'Africa/Windhoek',
- 'Nepal Standard Time': 'Asia/Katmandu',
- 'New Zealand Standard Time': 'Pacific/Auckland',
- 'Newfoundland Standard Time': 'America/St_Johns',
- 'Norfolk Standard Time': 'Pacific/Norfolk',
- 'North Asia East Standard Time': 'Asia/Irkutsk',
- 'North Asia Standard Time': 'Asia/Krasnoyarsk',
- 'North Korea Standard Time': 'Asia/Pyongyang',
- 'Omsk Standard Time': 'Asia/Omsk',
- 'Pacific SA Standard Time': 'America/Santiago',
- 'Pacific Standard Time': 'America/Los_Angeles',
- 'Pacific Standard Time (Mexico)': 'America/Tijuana',
- 'Pakistan Standard Time': 'Asia/Karachi',
- 'Paraguay Standard Time': 'America/Asuncion',
- 'Qyzylorda Standard Time': 'Asia/Qyzylorda',
- 'Romance Standard Time': 'Europe/Paris',
- 'Russia Time Zone 10': 'Asia/Srednekolymsk',
- 'Russia Time Zone 11': 'Asia/Kamchatka',
- 'Russia Time Zone 3': 'Europe/Samara',
- 'Russian Standard Time': 'Europe/Moscow',
- 'SA Eastern Standard Time': 'America/Cayenne',
- 'SA Pacific Standard Time': 'America/Bogota',
- 'SA Western Standard Time': 'America/La_Paz',
- 'SE Asia Standard Time': 'Asia/Bangkok',
- 'Saint Pierre Standard Time': 'America/Miquelon',
- 'Sakhalin Standard Time': 'Asia/Sakhalin',
- 'Samoa Standard Time': 'Pacific/Apia',
- 'Sao Tome Standard Time': 'Africa/Sao_Tome',
- 'Saratov Standard Time': 'Europe/Saratov',
- 'Singapore Standard Time': 'Asia/Singapore',
- 'South Africa Standard Time': 'Africa/Johannesburg',
- 'Sri Lanka Standard Time': 'Asia/Colombo',
- 'Sudan Standard Time': 'Africa/Khartoum',
- 'Syria Standard Time': 'Asia/Damascus',
- 'Taipei Standard Time': 'Asia/Taipei',
- 'Tasmania Standard Time': 'Australia/Hobart',
- 'Tocantins Standard Time': 'America/Araguaina',
- 'Tokyo Standard Time': 'Asia/Tokyo',
- 'Tomsk Standard Time': 'Asia/Tomsk',
- 'Tonga Standard Time': 'Pacific/Tongatapu',
- 'Transbaikal Standard Time': 'Asia/Chita',
- 'Turkey Standard Time': 'Europe/Istanbul',
- 'Turks And Caicos Standard Time': 'America/Grand_Turk',
- 'US Eastern Standard Time': 'America/Indianapolis',
- 'US Mountain Standard Time': 'America/Phoenix',
- 'UTC': 'Etc/GMT',
- 'UTC+12': 'Etc/GMT-12',
- 'UTC+13': 'Etc/GMT-13',
- 'UTC-02': 'Etc/GMT+2',
- 'UTC-08': 'Etc/GMT+8',
- 'UTC-09': 'Etc/GMT+9',
- 'UTC-11': 'Etc/GMT+11',
- 'Ulaanbaatar Standard Time': 'Asia/Ulaanbaatar',
- 'Venezuela Standard Time': 'America/Caracas',
- 'Vladivostok Standard Time': 'Asia/Vladivostok',
- 'Volgograd Standard Time': 'Europe/Volgograd',
- 'W. Australia Standard Time': 'Australia/Perth',
- 'W. Central Africa Standard Time': 'Africa/Lagos',
- 'W. Europe Standard Time': 'Europe/Berlin',
- 'W. Mongolia Standard Time': 'Asia/Hovd',
- 'West Asia Standard Time': 'Asia/Tashkent',
- 'West Bank Standard Time': 'Asia/Hebron',
- 'West Pacific Standard Time': 'Pacific/Port_Moresby',
- 'Yakutsk Standard Time': 'Asia/Yakutsk'}
-
-# Old name for the win_tz variable:
-tz_names = win_tz
-
-tz_win = {'Africa/Abidjan': 'Greenwich Standard Time',
- 'Africa/Accra': 'Greenwich Standard Time',
- 'Africa/Addis_Ababa': 'E. Africa Standard Time',
- 'Africa/Algiers': 'W. Central Africa Standard Time',
- 'Africa/Asmera': 'E. Africa Standard Time',
- 'Africa/Bamako': 'Greenwich Standard Time',
- 'Africa/Bangui': 'W. Central Africa Standard Time',
- 'Africa/Banjul': 'Greenwich Standard Time',
- 'Africa/Bissau': 'Greenwich Standard Time',
- 'Africa/Blantyre': 'South Africa Standard Time',
- 'Africa/Brazzaville': 'W. Central Africa Standard Time',
- 'Africa/Bujumbura': 'South Africa Standard Time',
- 'Africa/Cairo': 'Egypt Standard Time',
- 'Africa/Casablanca': 'Morocco Standard Time',
- 'Africa/Ceuta': 'Romance Standard Time',
- 'Africa/Conakry': 'Greenwich Standard Time',
- 'Africa/Dakar': 'Greenwich Standard Time',
- 'Africa/Dar_es_Salaam': 'E. Africa Standard Time',
- 'Africa/Djibouti': 'E. Africa Standard Time',
- 'Africa/Douala': 'W. Central Africa Standard Time',
- 'Africa/El_Aaiun': 'Morocco Standard Time',
- 'Africa/Freetown': 'Greenwich Standard Time',
- 'Africa/Gaborone': 'South Africa Standard Time',
- 'Africa/Harare': 'South Africa Standard Time',
- 'Africa/Johannesburg': 'South Africa Standard Time',
- 'Africa/Juba': 'E. Africa Standard Time',
- 'Africa/Kampala': 'E. Africa Standard Time',
- 'Africa/Khartoum': 'Sudan Standard Time',
- 'Africa/Kigali': 'South Africa Standard Time',
- 'Africa/Kinshasa': 'W. Central Africa Standard Time',
- 'Africa/Lagos': 'W. Central Africa Standard Time',
- 'Africa/Libreville': 'W. Central Africa Standard Time',
- 'Africa/Lome': 'Greenwich Standard Time',
- 'Africa/Luanda': 'W. Central Africa Standard Time',
- 'Africa/Lubumbashi': 'South Africa Standard Time',
- 'Africa/Lusaka': 'South Africa Standard Time',
- 'Africa/Malabo': 'W. Central Africa Standard Time',
- 'Africa/Maputo': 'South Africa Standard Time',
- 'Africa/Maseru': 'South Africa Standard Time',
- 'Africa/Mbabane': 'South Africa Standard Time',
- 'Africa/Mogadishu': 'E. Africa Standard Time',
- 'Africa/Monrovia': 'Greenwich Standard Time',
- 'Africa/Nairobi': 'E. Africa Standard Time',
- 'Africa/Ndjamena': 'W. Central Africa Standard Time',
- 'Africa/Niamey': 'W. Central Africa Standard Time',
- 'Africa/Nouakchott': 'Greenwich Standard Time',
- 'Africa/Ouagadougou': 'Greenwich Standard Time',
- 'Africa/Porto-Novo': 'W. Central Africa Standard Time',
- 'Africa/Sao_Tome': 'Sao Tome Standard Time',
- 'Africa/Timbuktu': 'Greenwich Standard Time',
- 'Africa/Tripoli': 'Libya Standard Time',
- 'Africa/Tunis': 'W. Central Africa Standard Time',
- 'Africa/Windhoek': 'Namibia Standard Time',
- 'America/Adak': 'Aleutian Standard Time',
- 'America/Anchorage': 'Alaskan Standard Time',
- 'America/Anguilla': 'SA Western Standard Time',
- 'America/Antigua': 'SA Western Standard Time',
- 'America/Araguaina': 'Tocantins Standard Time',
- 'America/Argentina/La_Rioja': 'Argentina Standard Time',
- 'America/Argentina/Rio_Gallegos': 'Argentina Standard Time',
- 'America/Argentina/Salta': 'Argentina Standard Time',
- 'America/Argentina/San_Juan': 'Argentina Standard Time',
- 'America/Argentina/San_Luis': 'Argentina Standard Time',
- 'America/Argentina/Tucuman': 'Argentina Standard Time',
- 'America/Argentina/Ushuaia': 'Argentina Standard Time',
- 'America/Aruba': 'SA Western Standard Time',
- 'America/Asuncion': 'Paraguay Standard Time',
- 'America/Atka': 'Aleutian Standard Time',
- 'America/Bahia': 'Bahia Standard Time',
- 'America/Bahia_Banderas': 'Central Standard Time (Mexico)',
- 'America/Barbados': 'SA Western Standard Time',
- 'America/Belem': 'SA Eastern Standard Time',
- 'America/Belize': 'Central America Standard Time',
- 'America/Blanc-Sablon': 'SA Western Standard Time',
- 'America/Boa_Vista': 'SA Western Standard Time',
- 'America/Bogota': 'SA Pacific Standard Time',
- 'America/Boise': 'Mountain Standard Time',
- 'America/Buenos_Aires': 'Argentina Standard Time',
- 'America/Cambridge_Bay': 'Mountain Standard Time',
- 'America/Campo_Grande': 'Central Brazilian Standard Time',
- 'America/Cancun': 'Eastern Standard Time (Mexico)',
- 'America/Caracas': 'Venezuela Standard Time',
- 'America/Catamarca': 'Argentina Standard Time',
- 'America/Cayenne': 'SA Eastern Standard Time',
- 'America/Cayman': 'SA Pacific Standard Time',
- 'America/Chicago': 'Central Standard Time',
- 'America/Chihuahua': 'Mountain Standard Time (Mexico)',
- 'America/Coral_Harbour': 'SA Pacific Standard Time',
- 'America/Cordoba': 'Argentina Standard Time',
- 'America/Costa_Rica': 'Central America Standard Time',
- 'America/Creston': 'US Mountain Standard Time',
- 'America/Cuiaba': 'Central Brazilian Standard Time',
- 'America/Curacao': 'SA Western Standard Time',
- 'America/Danmarkshavn': 'UTC',
- 'America/Dawson': 'Pacific Standard Time',
- 'America/Dawson_Creek': 'US Mountain Standard Time',
- 'America/Denver': 'Mountain Standard Time',
- 'America/Detroit': 'Eastern Standard Time',
- 'America/Dominica': 'SA Western Standard Time',
- 'America/Edmonton': 'Mountain Standard Time',
- 'America/Eirunepe': 'SA Pacific Standard Time',
- 'America/El_Salvador': 'Central America Standard Time',
- 'America/Ensenada': 'Pacific Standard Time (Mexico)',
- 'America/Fort_Nelson': 'US Mountain Standard Time',
- 'America/Fortaleza': 'SA Eastern Standard Time',
- 'America/Glace_Bay': 'Atlantic Standard Time',
- 'America/Godthab': 'Greenland Standard Time',
- 'America/Goose_Bay': 'Atlantic Standard Time',
- 'America/Grand_Turk': 'Turks And Caicos Standard Time',
- 'America/Grenada': 'SA Western Standard Time',
- 'America/Guadeloupe': 'SA Western Standard Time',
- 'America/Guatemala': 'Central America Standard Time',
- 'America/Guayaquil': 'SA Pacific Standard Time',
- 'America/Guyana': 'SA Western Standard Time',
- 'America/Halifax': 'Atlantic Standard Time',
- 'America/Havana': 'Cuba Standard Time',
- 'America/Hermosillo': 'US Mountain Standard Time',
- 'America/Indiana/Knox': 'Central Standard Time',
- 'America/Indiana/Marengo': 'US Eastern Standard Time',
- 'America/Indiana/Petersburg': 'Eastern Standard Time',
- 'America/Indiana/Tell_City': 'Central Standard Time',
- 'America/Indiana/Vevay': 'US Eastern Standard Time',
- 'America/Indiana/Vincennes': 'Eastern Standard Time',
- 'America/Indiana/Winamac': 'Eastern Standard Time',
- 'America/Indianapolis': 'US Eastern Standard Time',
- 'America/Inuvik': 'Mountain Standard Time',
- 'America/Iqaluit': 'Eastern Standard Time',
- 'America/Jamaica': 'SA Pacific Standard Time',
- 'America/Jujuy': 'Argentina Standard Time',
- 'America/Juneau': 'Alaskan Standard Time',
- 'America/Kentucky/Monticello': 'Eastern Standard Time',
- 'America/Knox_IN': 'Central Standard Time',
- 'America/Kralendijk': 'SA Western Standard Time',
- 'America/La_Paz': 'SA Western Standard Time',
- 'America/Lima': 'SA Pacific Standard Time',
- 'America/Los_Angeles': 'Pacific Standard Time',
- 'America/Louisville': 'Eastern Standard Time',
- 'America/Lower_Princes': 'SA Western Standard Time',
- 'America/Maceio': 'SA Eastern Standard Time',
- 'America/Managua': 'Central America Standard Time',
- 'America/Manaus': 'SA Western Standard Time',
- 'America/Marigot': 'SA Western Standard Time',
- 'America/Martinique': 'SA Western Standard Time',
- 'America/Matamoros': 'Central Standard Time',
- 'America/Mazatlan': 'Mountain Standard Time (Mexico)',
- 'America/Mendoza': 'Argentina Standard Time',
- 'America/Menominee': 'Central Standard Time',
- 'America/Merida': 'Central Standard Time (Mexico)',
- 'America/Metlakatla': 'Alaskan Standard Time',
- 'America/Mexico_City': 'Central Standard Time (Mexico)',
- 'America/Miquelon': 'Saint Pierre Standard Time',
- 'America/Moncton': 'Atlantic Standard Time',
- 'America/Monterrey': 'Central Standard Time (Mexico)',
- 'America/Montevideo': 'Montevideo Standard Time',
- 'America/Montreal': 'Eastern Standard Time',
- 'America/Montserrat': 'SA Western Standard Time',
- 'America/Nassau': 'Eastern Standard Time',
- 'America/New_York': 'Eastern Standard Time',
- 'America/Nipigon': 'Eastern Standard Time',
- 'America/Nome': 'Alaskan Standard Time',
- 'America/Noronha': 'UTC-02',
- 'America/North_Dakota/Beulah': 'Central Standard Time',
- 'America/North_Dakota/Center': 'Central Standard Time',
- 'America/North_Dakota/New_Salem': 'Central Standard Time',
- 'America/Ojinaga': 'Mountain Standard Time',
- 'America/Panama': 'SA Pacific Standard Time',
- 'America/Pangnirtung': 'Eastern Standard Time',
- 'America/Paramaribo': 'SA Eastern Standard Time',
- 'America/Phoenix': 'US Mountain Standard Time',
- 'America/Port-au-Prince': 'Haiti Standard Time',
- 'America/Port_of_Spain': 'SA Western Standard Time',
- 'America/Porto_Acre': 'SA Pacific Standard Time',
- 'America/Porto_Velho': 'SA Western Standard Time',
- 'America/Puerto_Rico': 'SA Western Standard Time',
- 'America/Punta_Arenas': 'Magallanes Standard Time',
- 'America/Rainy_River': 'Central Standard Time',
- 'America/Rankin_Inlet': 'Central Standard Time',
- 'America/Recife': 'SA Eastern Standard Time',
- 'America/Regina': 'Canada Central Standard Time',
- 'America/Resolute': 'Central Standard Time',
- 'America/Rio_Branco': 'SA Pacific Standard Time',
- 'America/Santa_Isabel': 'Pacific Standard Time (Mexico)',
- 'America/Santarem': 'SA Eastern Standard Time',
- 'America/Santiago': 'Pacific SA Standard Time',
- 'America/Santo_Domingo': 'SA Western Standard Time',
- 'America/Sao_Paulo': 'E. South America Standard Time',
- 'America/Scoresbysund': 'Azores Standard Time',
- 'America/Shiprock': 'Mountain Standard Time',
- 'America/Sitka': 'Alaskan Standard Time',
- 'America/St_Barthelemy': 'SA Western Standard Time',
- 'America/St_Johns': 'Newfoundland Standard Time',
- 'America/St_Kitts': 'SA Western Standard Time',
- 'America/St_Lucia': 'SA Western Standard Time',
- 'America/St_Thomas': 'SA Western Standard Time',
- 'America/St_Vincent': 'SA Western Standard Time',
- 'America/Swift_Current': 'Canada Central Standard Time',
- 'America/Tegucigalpa': 'Central America Standard Time',
- 'America/Thule': 'Atlantic Standard Time',
- 'America/Thunder_Bay': 'Eastern Standard Time',
- 'America/Tijuana': 'Pacific Standard Time (Mexico)',
- 'America/Toronto': 'Eastern Standard Time',
- 'America/Tortola': 'SA Western Standard Time',
- 'America/Vancouver': 'Pacific Standard Time',
- 'America/Virgin': 'SA Western Standard Time',
- 'America/Whitehorse': 'Pacific Standard Time',
- 'America/Winnipeg': 'Central Standard Time',
- 'America/Yakutat': 'Alaskan Standard Time',
- 'America/Yellowknife': 'Mountain Standard Time',
- 'Antarctica/Casey': 'Singapore Standard Time',
- 'Antarctica/Davis': 'SE Asia Standard Time',
- 'Antarctica/DumontDUrville': 'West Pacific Standard Time',
- 'Antarctica/Macquarie': 'Central Pacific Standard Time',
- 'Antarctica/Mawson': 'West Asia Standard Time',
- 'Antarctica/McMurdo': 'New Zealand Standard Time',
- 'Antarctica/Palmer': 'SA Eastern Standard Time',
- 'Antarctica/Rothera': 'SA Eastern Standard Time',
- 'Antarctica/South_Pole': 'New Zealand Standard Time',
- 'Antarctica/Syowa': 'E. Africa Standard Time',
- 'Antarctica/Vostok': 'Central Asia Standard Time',
- 'Arctic/Longyearbyen': 'W. Europe Standard Time',
- 'Asia/Aden': 'Arab Standard Time',
- 'Asia/Almaty': 'Central Asia Standard Time',
- 'Asia/Amman': 'Jordan Standard Time',
- 'Asia/Anadyr': 'Russia Time Zone 11',
- 'Asia/Aqtau': 'West Asia Standard Time',
- 'Asia/Aqtobe': 'West Asia Standard Time',
- 'Asia/Ashgabat': 'West Asia Standard Time',
- 'Asia/Ashkhabad': 'West Asia Standard Time',
- 'Asia/Atyrau': 'West Asia Standard Time',
- 'Asia/Baghdad': 'Arabic Standard Time',
- 'Asia/Bahrain': 'Arab Standard Time',
- 'Asia/Baku': 'Azerbaijan Standard Time',
- 'Asia/Bangkok': 'SE Asia Standard Time',
- 'Asia/Barnaul': 'Altai Standard Time',
- 'Asia/Beirut': 'Middle East Standard Time',
- 'Asia/Bishkek': 'Central Asia Standard Time',
- 'Asia/Brunei': 'Singapore Standard Time',
- 'Asia/Calcutta': 'India Standard Time',
- 'Asia/Chita': 'Transbaikal Standard Time',
- 'Asia/Choibalsan': 'Ulaanbaatar Standard Time',
- 'Asia/Chongqing': 'China Standard Time',
- 'Asia/Chungking': 'China Standard Time',
- 'Asia/Colombo': 'Sri Lanka Standard Time',
- 'Asia/Dacca': 'Bangladesh Standard Time',
- 'Asia/Damascus': 'Syria Standard Time',
- 'Asia/Dhaka': 'Bangladesh Standard Time',
- 'Asia/Dili': 'Tokyo Standard Time',
- 'Asia/Dubai': 'Arabian Standard Time',
- 'Asia/Dushanbe': 'West Asia Standard Time',
- 'Asia/Famagusta': 'GTB Standard Time',
- 'Asia/Gaza': 'West Bank Standard Time',
- 'Asia/Harbin': 'China Standard Time',
- 'Asia/Hebron': 'West Bank Standard Time',
- 'Asia/Hong_Kong': 'China Standard Time',
- 'Asia/Hovd': 'W. Mongolia Standard Time',
- 'Asia/Irkutsk': 'North Asia East Standard Time',
- 'Asia/Jakarta': 'SE Asia Standard Time',
- 'Asia/Jayapura': 'Tokyo Standard Time',
- 'Asia/Jerusalem': 'Israel Standard Time',
- 'Asia/Kabul': 'Afghanistan Standard Time',
- 'Asia/Kamchatka': 'Russia Time Zone 11',
- 'Asia/Karachi': 'Pakistan Standard Time',
- 'Asia/Kashgar': 'Central Asia Standard Time',
- 'Asia/Katmandu': 'Nepal Standard Time',
- 'Asia/Khandyga': 'Yakutsk Standard Time',
- 'Asia/Krasnoyarsk': 'North Asia Standard Time',
- 'Asia/Kuala_Lumpur': 'Singapore Standard Time',
- 'Asia/Kuching': 'Singapore Standard Time',
- 'Asia/Kuwait': 'Arab Standard Time',
- 'Asia/Macao': 'China Standard Time',
- 'Asia/Macau': 'China Standard Time',
- 'Asia/Magadan': 'Magadan Standard Time',
- 'Asia/Makassar': 'Singapore Standard Time',
- 'Asia/Manila': 'Singapore Standard Time',
- 'Asia/Muscat': 'Arabian Standard Time',
- 'Asia/Nicosia': 'GTB Standard Time',
- 'Asia/Novokuznetsk': 'North Asia Standard Time',
- 'Asia/Novosibirsk': 'N. Central Asia Standard Time',
- 'Asia/Omsk': 'Omsk Standard Time',
- 'Asia/Oral': 'West Asia Standard Time',
- 'Asia/Phnom_Penh': 'SE Asia Standard Time',
- 'Asia/Pontianak': 'SE Asia Standard Time',
- 'Asia/Pyongyang': 'North Korea Standard Time',
- 'Asia/Qatar': 'Arab Standard Time',
- 'Asia/Qostanay': 'Central Asia Standard Time',
- 'Asia/Qyzylorda': 'Qyzylorda Standard Time',
- 'Asia/Rangoon': 'Myanmar Standard Time',
- 'Asia/Riyadh': 'Arab Standard Time',
- 'Asia/Saigon': 'SE Asia Standard Time',
- 'Asia/Sakhalin': 'Sakhalin Standard Time',
- 'Asia/Samarkand': 'West Asia Standard Time',
- 'Asia/Seoul': 'Korea Standard Time',
- 'Asia/Shanghai': 'China Standard Time',
- 'Asia/Singapore': 'Singapore Standard Time',
- 'Asia/Srednekolymsk': 'Russia Time Zone 10',
- 'Asia/Taipei': 'Taipei Standard Time',
- 'Asia/Tashkent': 'West Asia Standard Time',
- 'Asia/Tbilisi': 'Georgian Standard Time',
- 'Asia/Tehran': 'Iran Standard Time',
- 'Asia/Tel_Aviv': 'Israel Standard Time',
- 'Asia/Thimbu': 'Bangladesh Standard Time',
- 'Asia/Thimphu': 'Bangladesh Standard Time',
- 'Asia/Tokyo': 'Tokyo Standard Time',
- 'Asia/Tomsk': 'Tomsk Standard Time',
- 'Asia/Ujung_Pandang': 'Singapore Standard Time',
- 'Asia/Ulaanbaatar': 'Ulaanbaatar Standard Time',
- 'Asia/Ulan_Bator': 'Ulaanbaatar Standard Time',
- 'Asia/Urumqi': 'Central Asia Standard Time',
- 'Asia/Ust-Nera': 'Vladivostok Standard Time',
- 'Asia/Vientiane': 'SE Asia Standard Time',
- 'Asia/Vladivostok': 'Vladivostok Standard Time',
- 'Asia/Yakutsk': 'Yakutsk Standard Time',
- 'Asia/Yekaterinburg': 'Ekaterinburg Standard Time',
- 'Asia/Yerevan': 'Caucasus Standard Time',
- 'Atlantic/Azores': 'Azores Standard Time',
- 'Atlantic/Bermuda': 'Atlantic Standard Time',
- 'Atlantic/Canary': 'GMT Standard Time',
- 'Atlantic/Cape_Verde': 'Cape Verde Standard Time',
- 'Atlantic/Faeroe': 'GMT Standard Time',
- 'Atlantic/Jan_Mayen': 'W. Europe Standard Time',
- 'Atlantic/Madeira': 'GMT Standard Time',
- 'Atlantic/Reykjavik': 'Greenwich Standard Time',
- 'Atlantic/South_Georgia': 'UTC-02',
- 'Atlantic/St_Helena': 'Greenwich Standard Time',
- 'Atlantic/Stanley': 'SA Eastern Standard Time',
- 'Australia/ACT': 'AUS Eastern Standard Time',
- 'Australia/Adelaide': 'Cen. Australia Standard Time',
- 'Australia/Brisbane': 'E. Australia Standard Time',
- 'Australia/Broken_Hill': 'Cen. Australia Standard Time',
- 'Australia/Canberra': 'AUS Eastern Standard Time',
- 'Australia/Currie': 'Tasmania Standard Time',
- 'Australia/Darwin': 'AUS Central Standard Time',
- 'Australia/Eucla': 'Aus Central W. Standard Time',
- 'Australia/Hobart': 'Tasmania Standard Time',
- 'Australia/LHI': 'Lord Howe Standard Time',
- 'Australia/Lindeman': 'E. Australia Standard Time',
- 'Australia/Lord_Howe': 'Lord Howe Standard Time',
- 'Australia/Melbourne': 'AUS Eastern Standard Time',
- 'Australia/NSW': 'AUS Eastern Standard Time',
- 'Australia/North': 'AUS Central Standard Time',
- 'Australia/Perth': 'W. Australia Standard Time',
- 'Australia/Queensland': 'E. Australia Standard Time',
- 'Australia/South': 'Cen. Australia Standard Time',
- 'Australia/Sydney': 'AUS Eastern Standard Time',
- 'Australia/Tasmania': 'Tasmania Standard Time',
- 'Australia/Victoria': 'AUS Eastern Standard Time',
- 'Australia/West': 'W. Australia Standard Time',
- 'Australia/Yancowinna': 'Cen. Australia Standard Time',
- 'Brazil/Acre': 'SA Pacific Standard Time',
- 'Brazil/DeNoronha': 'UTC-02',
- 'Brazil/East': 'E. South America Standard Time',
- 'Brazil/West': 'SA Western Standard Time',
- 'CST6CDT': 'Central Standard Time',
- 'Canada/Atlantic': 'Atlantic Standard Time',
- 'Canada/Central': 'Central Standard Time',
- 'Canada/Eastern': 'Eastern Standard Time',
- 'Canada/Mountain': 'Mountain Standard Time',
- 'Canada/Newfoundland': 'Newfoundland Standard Time',
- 'Canada/Pacific': 'Pacific Standard Time',
- 'Canada/Saskatchewan': 'Canada Central Standard Time',
- 'Canada/Yukon': 'Pacific Standard Time',
- 'Chile/Continental': 'Pacific SA Standard Time',
- 'Chile/EasterIsland': 'Easter Island Standard Time',
- 'Cuba': 'Cuba Standard Time',
- 'EST5EDT': 'Eastern Standard Time',
- 'Egypt': 'Egypt Standard Time',
- 'Eire': 'GMT Standard Time',
- 'Etc/GMT': 'UTC',
- 'Etc/GMT+1': 'Cape Verde Standard Time',
- 'Etc/GMT+10': 'Hawaiian Standard Time',
- 'Etc/GMT+11': 'UTC-11',
- 'Etc/GMT+12': 'Dateline Standard Time',
- 'Etc/GMT+2': 'UTC-02',
- 'Etc/GMT+3': 'SA Eastern Standard Time',
- 'Etc/GMT+4': 'SA Western Standard Time',
- 'Etc/GMT+5': 'SA Pacific Standard Time',
- 'Etc/GMT+6': 'Central America Standard Time',
- 'Etc/GMT+7': 'US Mountain Standard Time',
- 'Etc/GMT+8': 'UTC-08',
- 'Etc/GMT+9': 'UTC-09',
- 'Etc/GMT-1': 'W. Central Africa Standard Time',
- 'Etc/GMT-10': 'West Pacific Standard Time',
- 'Etc/GMT-11': 'Central Pacific Standard Time',
- 'Etc/GMT-12': 'UTC+12',
- 'Etc/GMT-13': 'UTC+13',
- 'Etc/GMT-14': 'Line Islands Standard Time',
- 'Etc/GMT-2': 'South Africa Standard Time',
- 'Etc/GMT-3': 'E. Africa Standard Time',
- 'Etc/GMT-4': 'Arabian Standard Time',
- 'Etc/GMT-5': 'West Asia Standard Time',
- 'Etc/GMT-6': 'Central Asia Standard Time',
- 'Etc/GMT-7': 'SE Asia Standard Time',
- 'Etc/GMT-8': 'Singapore Standard Time',
- 'Etc/GMT-9': 'Tokyo Standard Time',
- 'Etc/UCT': 'UTC',
- 'Etc/UTC': 'UTC',
- 'Europe/Amsterdam': 'W. Europe Standard Time',
- 'Europe/Andorra': 'W. Europe Standard Time',
- 'Europe/Astrakhan': 'Astrakhan Standard Time',
- 'Europe/Athens': 'GTB Standard Time',
- 'Europe/Belfast': 'GMT Standard Time',
- 'Europe/Belgrade': 'Central Europe Standard Time',
- 'Europe/Berlin': 'W. Europe Standard Time',
- 'Europe/Bratislava': 'Central Europe Standard Time',
- 'Europe/Brussels': 'Romance Standard Time',
- 'Europe/Bucharest': 'GTB Standard Time',
- 'Europe/Budapest': 'Central Europe Standard Time',
- 'Europe/Busingen': 'W. Europe Standard Time',
- 'Europe/Chisinau': 'E. Europe Standard Time',
- 'Europe/Copenhagen': 'Romance Standard Time',
- 'Europe/Dublin': 'GMT Standard Time',
- 'Europe/Gibraltar': 'W. Europe Standard Time',
- 'Europe/Guernsey': 'GMT Standard Time',
- 'Europe/Helsinki': 'FLE Standard Time',
- 'Europe/Isle_of_Man': 'GMT Standard Time',
- 'Europe/Istanbul': 'Turkey Standard Time',
- 'Europe/Jersey': 'GMT Standard Time',
- 'Europe/Kaliningrad': 'Kaliningrad Standard Time',
- 'Europe/Kiev': 'FLE Standard Time',
- 'Europe/Kirov': 'Russian Standard Time',
- 'Europe/Lisbon': 'GMT Standard Time',
- 'Europe/Ljubljana': 'Central Europe Standard Time',
- 'Europe/London': 'GMT Standard Time',
- 'Europe/Luxembourg': 'W. Europe Standard Time',
- 'Europe/Madrid': 'Romance Standard Time',
- 'Europe/Malta': 'W. Europe Standard Time',
- 'Europe/Mariehamn': 'FLE Standard Time',
- 'Europe/Minsk': 'Belarus Standard Time',
- 'Europe/Monaco': 'W. Europe Standard Time',
- 'Europe/Moscow': 'Russian Standard Time',
- 'Europe/Oslo': 'W. Europe Standard Time',
- 'Europe/Paris': 'Romance Standard Time',
- 'Europe/Podgorica': 'Central Europe Standard Time',
- 'Europe/Prague': 'Central Europe Standard Time',
- 'Europe/Riga': 'FLE Standard Time',
- 'Europe/Rome': 'W. Europe Standard Time',
- 'Europe/Samara': 'Russia Time Zone 3',
- 'Europe/San_Marino': 'W. Europe Standard Time',
- 'Europe/Sarajevo': 'Central European Standard Time',
- 'Europe/Saratov': 'Saratov Standard Time',
- 'Europe/Simferopol': 'Russian Standard Time',
- 'Europe/Skopje': 'Central European Standard Time',
- 'Europe/Sofia': 'FLE Standard Time',
- 'Europe/Stockholm': 'W. Europe Standard Time',
- 'Europe/Tallinn': 'FLE Standard Time',
- 'Europe/Tirane': 'Central Europe Standard Time',
- 'Europe/Tiraspol': 'E. Europe Standard Time',
- 'Europe/Ulyanovsk': 'Astrakhan Standard Time',
- 'Europe/Uzhgorod': 'FLE Standard Time',
- 'Europe/Vaduz': 'W. Europe Standard Time',
- 'Europe/Vatican': 'W. Europe Standard Time',
- 'Europe/Vienna': 'W. Europe Standard Time',
- 'Europe/Vilnius': 'FLE Standard Time',
- 'Europe/Volgograd': 'Volgograd Standard Time',
- 'Europe/Warsaw': 'Central European Standard Time',
- 'Europe/Zagreb': 'Central European Standard Time',
- 'Europe/Zaporozhye': 'FLE Standard Time',
- 'Europe/Zurich': 'W. Europe Standard Time',
- 'GB': 'GMT Standard Time',
- 'GB-Eire': 'GMT Standard Time',
- 'GMT+0': 'UTC',
- 'GMT-0': 'UTC',
- 'GMT0': 'UTC',
- 'Greenwich': 'UTC',
- 'Hongkong': 'China Standard Time',
- 'Iceland': 'Greenwich Standard Time',
- 'Indian/Antananarivo': 'E. Africa Standard Time',
- 'Indian/Chagos': 'Central Asia Standard Time',
- 'Indian/Christmas': 'SE Asia Standard Time',
- 'Indian/Cocos': 'Myanmar Standard Time',
- 'Indian/Comoro': 'E. Africa Standard Time',
- 'Indian/Kerguelen': 'West Asia Standard Time',
- 'Indian/Mahe': 'Mauritius Standard Time',
- 'Indian/Maldives': 'West Asia Standard Time',
- 'Indian/Mauritius': 'Mauritius Standard Time',
- 'Indian/Mayotte': 'E. Africa Standard Time',
- 'Indian/Reunion': 'Mauritius Standard Time',
- 'Iran': 'Iran Standard Time',
- 'Israel': 'Israel Standard Time',
- 'Jamaica': 'SA Pacific Standard Time',
- 'Japan': 'Tokyo Standard Time',
- 'Kwajalein': 'UTC+12',
- 'Libya': 'Libya Standard Time',
- 'MST7MDT': 'Mountain Standard Time',
- 'Mexico/BajaNorte': 'Pacific Standard Time (Mexico)',
- 'Mexico/BajaSur': 'Mountain Standard Time (Mexico)',
- 'Mexico/General': 'Central Standard Time (Mexico)',
- 'NZ': 'New Zealand Standard Time',
- 'NZ-CHAT': 'Chatham Islands Standard Time',
- 'Navajo': 'Mountain Standard Time',
- 'PRC': 'China Standard Time',
- 'PST8PDT': 'Pacific Standard Time',
- 'Pacific/Apia': 'Samoa Standard Time',
- 'Pacific/Auckland': 'New Zealand Standard Time',
- 'Pacific/Bougainville': 'Bougainville Standard Time',
- 'Pacific/Chatham': 'Chatham Islands Standard Time',
- 'Pacific/Easter': 'Easter Island Standard Time',
- 'Pacific/Efate': 'Central Pacific Standard Time',
- 'Pacific/Enderbury': 'UTC+13',
- 'Pacific/Fakaofo': 'UTC+13',
- 'Pacific/Fiji': 'Fiji Standard Time',
- 'Pacific/Funafuti': 'UTC+12',
- 'Pacific/Galapagos': 'Central America Standard Time',
- 'Pacific/Gambier': 'UTC-09',
- 'Pacific/Guadalcanal': 'Central Pacific Standard Time',
- 'Pacific/Guam': 'West Pacific Standard Time',
- 'Pacific/Honolulu': 'Hawaiian Standard Time',
- 'Pacific/Johnston': 'Hawaiian Standard Time',
- 'Pacific/Kiritimati': 'Line Islands Standard Time',
- 'Pacific/Kosrae': 'Central Pacific Standard Time',
- 'Pacific/Kwajalein': 'UTC+12',
- 'Pacific/Majuro': 'UTC+12',
- 'Pacific/Marquesas': 'Marquesas Standard Time',
- 'Pacific/Midway': 'UTC-11',
- 'Pacific/Nauru': 'UTC+12',
- 'Pacific/Niue': 'UTC-11',
- 'Pacific/Norfolk': 'Norfolk Standard Time',
- 'Pacific/Noumea': 'Central Pacific Standard Time',
- 'Pacific/Pago_Pago': 'UTC-11',
- 'Pacific/Palau': 'Tokyo Standard Time',
- 'Pacific/Pitcairn': 'UTC-08',
- 'Pacific/Ponape': 'Central Pacific Standard Time',
- 'Pacific/Port_Moresby': 'West Pacific Standard Time',
- 'Pacific/Rarotonga': 'Hawaiian Standard Time',
- 'Pacific/Saipan': 'West Pacific Standard Time',
- 'Pacific/Samoa': 'UTC-11',
- 'Pacific/Tahiti': 'Hawaiian Standard Time',
- 'Pacific/Tarawa': 'UTC+12',
- 'Pacific/Tongatapu': 'Tonga Standard Time',
- 'Pacific/Truk': 'West Pacific Standard Time',
- 'Pacific/Wake': 'UTC+12',
- 'Pacific/Wallis': 'UTC+12',
- 'Poland': 'Central European Standard Time',
- 'Portugal': 'GMT Standard Time',
- 'ROC': 'Taipei Standard Time',
- 'ROK': 'Korea Standard Time',
- 'Singapore': 'Singapore Standard Time',
- 'Turkey': 'Turkey Standard Time',
- 'UCT': 'UTC',
- 'US/Alaska': 'Alaskan Standard Time',
- 'US/Aleutian': 'Aleutian Standard Time',
- 'US/Arizona': 'US Mountain Standard Time',
- 'US/Central': 'Central Standard Time',
- 'US/Eastern': 'Eastern Standard Time',
- 'US/Hawaii': 'Hawaiian Standard Time',
- 'US/Indiana-Starke': 'Central Standard Time',
- 'US/Michigan': 'Eastern Standard Time',
- 'US/Mountain': 'Mountain Standard Time',
- 'US/Pacific': 'Pacific Standard Time',
- 'US/Samoa': 'UTC-11',
- 'UTC': 'UTC',
- 'Universal': 'UTC',
- 'W-SU': 'Russian Standard Time',
- 'Zulu': 'UTC'}
diff --git a/contrib/python/tzlocal/py2/ya.make b/contrib/python/tzlocal/py2/ya.make
deleted file mode 100644
index bb647f9e4b1..00000000000
--- a/contrib/python/tzlocal/py2/ya.make
+++ /dev/null
@@ -1,34 +0,0 @@
-# Generated by devtools/yamaker (pypi).
-
-PY2_LIBRARY()
-
-VERSION(2.1)
-
-LICENSE(MIT)
-
-PEERDIR(
- contrib/python/pytz
-)
-
-NO_LINT()
-
-NO_CHECK_IMPORTS(
- tzlocal.win32
-)
-
-PY_SRCS(
- TOP_LEVEL
- tzlocal/__init__.py
- tzlocal/unix.py
- tzlocal/utils.py
- tzlocal/win32.py
- tzlocal/windows_tz.py
-)
-
-RESOURCE_FILES(
- PREFIX contrib/python/tzlocal/py2/
- .dist-info/METADATA
- .dist-info/top_level.txt
-)
-
-END()
diff --git a/contrib/python/tzlocal/py3/.dist-info/METADATA b/contrib/python/tzlocal/py3/.dist-info/METADATA
deleted file mode 100644
index c41851c9f81..00000000000
--- a/contrib/python/tzlocal/py3/.dist-info/METADATA
+++ /dev/null
@@ -1,248 +0,0 @@
-Metadata-Version: 2.1
-Name: tzlocal
-Version: 5.2
-Summary: tzinfo object for the local timezone
-Author-email: Lennart Regebro <[email protected]>
-License: MIT
-Project-URL: Source code, https://github.com/regebro/tzlocal
-Project-URL: Changelog, https://github.com/regebro/tzlocal/blob/master/CHANGES.txt
-Project-URL: Issue tracker, https://github.com/regebro/tzlocal/issues
-Keywords: timezone
-Classifier: Development Status :: 5 - Production/Stable
-Classifier: License :: OSI Approved :: MIT License
-Classifier: Operating System :: Microsoft :: Windows
-Classifier: Operating System :: Unix
-Classifier: Operating System :: MacOS :: MacOS X
-Classifier: Typing :: Typed
-Classifier: Programming Language :: Python :: 3.8
-Classifier: Programming Language :: Python :: 3.9
-Classifier: Programming Language :: Python :: 3.10
-Classifier: Programming Language :: Python :: 3.11
-Classifier: Programming Language :: Python :: 3.12
-Requires-Python: >=3.8
-Description-Content-Type: text/x-rst
-License-File: LICENSE.txt
-Requires-Dist: tzdata ; platform_system == "Windows"
-Requires-Dist: backports.zoneinfo ; python_version < "3.9"
-Provides-Extra: devenv
-Requires-Dist: pytest (>=4.3) ; extra == 'devenv'
-Requires-Dist: pytest-mock (>=3.3) ; extra == 'devenv'
-Requires-Dist: pytest-cov ; extra == 'devenv'
-Requires-Dist: check-manifest ; extra == 'devenv'
-Requires-Dist: zest.releaser ; extra == 'devenv'
-
-tzlocal
-=======
-
-API CHANGE!
------------
-
-With version 3.0 of tzlocal, tzlocal no longer returned `pytz` objects, but
-`zoneinfo` objects, which has a different API. Since 4.0, it now restored
-partial compatibility for `pytz` users through Paul Ganssle's
-`pytz_deprecation_shim`.
-
-tzlocal 4.0 also adds an official function `get_localzone_name()` to get only
-the timezone name, instead of a timezone object. On unix, it can raise an
-error if you don't have a timezone name configured, where `get_localzone()`
-will succeed, so only use that if you need the timezone name.
-
-4.0 also adds way more information on what is going wrong in your
-configuration when the configuration files are unclear or contradictory.
-
-Version 5.0 removes the `pytz_deprecation_shim`, and now only returns
-`zoneinfo` objects, like verion 3.0 did. If you need `pytz` objects, you have
-to stay on version 4.0. If there are bugs in version 4.0, I will release
-updates, but there will be no further functional changes on the 4.x branch.
-
-
-Info
-----
-
-This Python module returns the `IANA time zone name
-<https://www.iana.org/time-zones>`_ for your local time zone or a ``tzinfo``
-object with the local timezone information, under Unix and Windows.
-
-It requires Python 3.8 or later, and will use the ``backports.tzinfo``
-package, for Python 3.8.
-
-This module attempts to fix a glaring hole in the ``pytz`` and ``zoneinfo``
-modules, that there is no way to get the local timezone information, unless
-you know the zoneinfo name, and under several Linux distros that's hard or
-impossible to figure out.
-
-With ``tzlocal`` you only need to call ``get_localzone()`` and you will get a
-``tzinfo`` object with the local time zone info. On some Unices you will
-still not get to know what the timezone name is, but you don't need that when
-you have the tzinfo file. However, if the timezone name is readily available
-it will be used.
-
-What it's not for
------------------
-
-It's not for converting the current time between UTC and your local time. There are
-other, simpler ways of doing this. This is ig you need to know things like the name
-of the time zone, or if you need to be able to convert between your time zone and
-another time zone for times that are in the future or in the past.
-
-For current time conversions to and from UTC, look in the Python ``time`` module.
-
-
-Supported systems
------------------
-
-These are the systems that are in theory supported:
-
- * Windows 2000 and later
-
- * Any unix-like system with a ``/etc/localtime`` or ``/usr/local/etc/localtime``
-
-If you have one of the above systems and it does not work, it's a bug.
-Please report it.
-
-Please note that if you are getting a time zone called ``local``, this is not
-a bug, it's actually the main feature of ``tzlocal``, that even if your
-system does NOT have a configuration file with the zoneinfo name of your time
-zone, it will still work.
-
-You can also use ``tzlocal`` to get the name of your local timezone, but only
-if your system is configured to make that possible. ``tzlocal`` looks for the
-timezone name in ``/etc/timezone``, ``/var/db/zoneinfo``,
-``/etc/sysconfig/clock`` and ``/etc/conf.d/clock``. If your
-``/etc/localtime`` is a symlink it can also extract the name from that
-symlink.
-
-If you need the name of your local time zone, then please make sure your
-system is properly configured to allow that.
-
-If your unix system doesn't have a timezone configured, tzlocal will default
-to UTC.
-
-Notes on Docker
----------------
-
-It turns out that Docker images frequently have broken timezone setups.
-This usually resuts in a warning that the configuration is wrong, or that
-the timezone offset doesn't match the found timezone.
-
-The easiest way to fix that is to set a TZ variable in your docker setup
-to whatever timezone you want, which is usually the timezone your host
-computer has.
-
-Usage
------
-
-Load the local timezone:
-
- >>> from tzlocal import get_localzone
- >>> tz = get_localzone()
- >>> tz
- zoneinfo.ZoneInfo(key='Europe/Warsaw')
-
-Create a local datetime:
-
- >>> from datetime import datetime
- >>> dt = datetime(2015, 4, 10, 7, 22, tzinfo=tz)
- >>> dt
- datetime.datetime(2015, 4, 10, 7, 22, tzinfo=zoneinfo.ZoneInfo(key='Europe/Warsaw'))
-
-Lookup another timezone with ``zoneinfo`` (``backports.zoneinfo`` on Python 3.8 or earlier):
-
- >>> from zoneinfo import ZoneInfo
- >>> eastern = ZoneInfo('US/Eastern')
-
-Convert the datetime:
-
- >>> dt.astimezone(eastern)
- datetime.datetime(2015, 4, 10, 1, 22, tzinfo=zoneinfo.ZoneInfo(key='US/Eastern'))
-
-If you just want the name of the local timezone, use `get_localzone_name()`:
-
- >>> from tzlocal import get_localzone_name
- >>> get_localzone_name()
- "Europe/Warsaw"
-
-Please note that under Unix, `get_localzone_name()` may fail if there is no zone
-configured, where `get_localzone()` would generally succeed.
-
-Troubleshooting
----------------
-
-If you don't get the result you expect, try running it with debugging turned on.
-Start a python interpreter that has tzlocal installed, and run the following code::
-
- import logging
- logging.basicConfig(level="DEBUG")
- import tzlocal
- tzlocal.get_localzone()
-
-The output should look something like this, and this will tell you what
-configurations were found::
-
- DEBUG:root:/etc/timezone found, contents:
- Europe/Warsaw
-
- DEBUG:root:/etc/localtime found
- DEBUG:root:2 found:
- {'/etc/timezone': 'Europe/Warsaw', '/etc/localtime is a symlink to': 'Europe/Warsaw'}
- zoneinfo.ZoneInfo(key='Europe/Warsaw')
-
-
-Development
------------
-
-For ease of development, there is a Makefile that will help you with basic tasks,
-like creating a development environment with all the necessary tools (although
-you need a supported Python version installed first)::
-
- $ make devenv
-
-To run tests::
-
- $ make test
-
-Check the syntax::
-
- $ make check
-
-
-Maintainer
-----------
-
-* Lennart Regebro, [email protected]
-
-Contributors
-------------
-
-* Marc Van Olmen
-* Benjamen Meyer
-* Manuel Ebert
-* Xiaokun Zhu
-* Cameris
-* Edward Betts
-* McK KIM
-* Cris Ewing
-* Ayala Shachar
-* Lev Maximov
-* Jakub Wilk
-* John Quarles
-* Preston Landers
-* Victor Torres
-* Jean Jordaan
-* Zackary Welch
-* Mickaël Schoentgen
-* Gabriel Corona
-* Alex Grönholm
-* Julin S
-* Miroslav Šedivý
-* revansSZ
-* Sam Treweek
-* Peter Di Pasquale
-* Rongrong
-
-(Sorry if I forgot someone)
-
-License
--------
-
-* MIT https://opensource.org/licenses/MIT
diff --git a/contrib/python/tzlocal/py3/.dist-info/top_level.txt b/contrib/python/tzlocal/py3/.dist-info/top_level.txt
deleted file mode 100644
index cd5e9b12a4b..00000000000
--- a/contrib/python/tzlocal/py3/.dist-info/top_level.txt
+++ /dev/null
@@ -1 +0,0 @@
-tzlocal
diff --git a/contrib/python/tzlocal/py3/LICENSE.txt b/contrib/python/tzlocal/py3/LICENSE.txt
deleted file mode 100644
index 9be1d2fe595..00000000000
--- a/contrib/python/tzlocal/py3/LICENSE.txt
+++ /dev/null
@@ -1,19 +0,0 @@
-Copyright 2011-2017 Lennart Regebro
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/contrib/python/tzlocal/py3/README.rst b/contrib/python/tzlocal/py3/README.rst
deleted file mode 100644
index 3dd27d68c90..00000000000
--- a/contrib/python/tzlocal/py3/README.rst
+++ /dev/null
@@ -1,215 +0,0 @@
-tzlocal
-=======
-
-API CHANGE!
------------
-
-With version 3.0 of tzlocal, tzlocal no longer returned `pytz` objects, but
-`zoneinfo` objects, which has a different API. Since 4.0, it now restored
-partial compatibility for `pytz` users through Paul Ganssle's
-`pytz_deprecation_shim`.
-
-tzlocal 4.0 also adds an official function `get_localzone_name()` to get only
-the timezone name, instead of a timezone object. On unix, it can raise an
-error if you don't have a timezone name configured, where `get_localzone()`
-will succeed, so only use that if you need the timezone name.
-
-4.0 also adds way more information on what is going wrong in your
-configuration when the configuration files are unclear or contradictory.
-
-Version 5.0 removes the `pytz_deprecation_shim`, and now only returns
-`zoneinfo` objects, like verion 3.0 did. If you need `pytz` objects, you have
-to stay on version 4.0. If there are bugs in version 4.0, I will release
-updates, but there will be no further functional changes on the 4.x branch.
-
-
-Info
-----
-
-This Python module returns the `IANA time zone name
-<https://www.iana.org/time-zones>`_ for your local time zone or a ``tzinfo``
-object with the local timezone information, under Unix and Windows.
-
-It requires Python 3.8 or later, and will use the ``backports.tzinfo``
-package, for Python 3.8.
-
-This module attempts to fix a glaring hole in the ``pytz`` and ``zoneinfo``
-modules, that there is no way to get the local timezone information, unless
-you know the zoneinfo name, and under several Linux distros that's hard or
-impossible to figure out.
-
-With ``tzlocal`` you only need to call ``get_localzone()`` and you will get a
-``tzinfo`` object with the local time zone info. On some Unices you will
-still not get to know what the timezone name is, but you don't need that when
-you have the tzinfo file. However, if the timezone name is readily available
-it will be used.
-
-What it's not for
------------------
-
-It's not for converting the current time between UTC and your local time. There are
-other, simpler ways of doing this. This is ig you need to know things like the name
-of the time zone, or if you need to be able to convert between your time zone and
-another time zone for times that are in the future or in the past.
-
-For current time conversions to and from UTC, look in the Python ``time`` module.
-
-
-Supported systems
------------------
-
-These are the systems that are in theory supported:
-
- * Windows 2000 and later
-
- * Any unix-like system with a ``/etc/localtime`` or ``/usr/local/etc/localtime``
-
-If you have one of the above systems and it does not work, it's a bug.
-Please report it.
-
-Please note that if you are getting a time zone called ``local``, this is not
-a bug, it's actually the main feature of ``tzlocal``, that even if your
-system does NOT have a configuration file with the zoneinfo name of your time
-zone, it will still work.
-
-You can also use ``tzlocal`` to get the name of your local timezone, but only
-if your system is configured to make that possible. ``tzlocal`` looks for the
-timezone name in ``/etc/timezone``, ``/var/db/zoneinfo``,
-``/etc/sysconfig/clock`` and ``/etc/conf.d/clock``. If your
-``/etc/localtime`` is a symlink it can also extract the name from that
-symlink.
-
-If you need the name of your local time zone, then please make sure your
-system is properly configured to allow that.
-
-If your unix system doesn't have a timezone configured, tzlocal will default
-to UTC.
-
-Notes on Docker
----------------
-
-It turns out that Docker images frequently have broken timezone setups.
-This usually resuts in a warning that the configuration is wrong, or that
-the timezone offset doesn't match the found timezone.
-
-The easiest way to fix that is to set a TZ variable in your docker setup
-to whatever timezone you want, which is usually the timezone your host
-computer has.
-
-Usage
------
-
-Load the local timezone:
-
- >>> from tzlocal import get_localzone
- >>> tz = get_localzone()
- >>> tz
- zoneinfo.ZoneInfo(key='Europe/Warsaw')
-
-Create a local datetime:
-
- >>> from datetime import datetime
- >>> dt = datetime(2015, 4, 10, 7, 22, tzinfo=tz)
- >>> dt
- datetime.datetime(2015, 4, 10, 7, 22, tzinfo=zoneinfo.ZoneInfo(key='Europe/Warsaw'))
-
-Lookup another timezone with ``zoneinfo`` (``backports.zoneinfo`` on Python 3.8 or earlier):
-
- >>> from zoneinfo import ZoneInfo
- >>> eastern = ZoneInfo('US/Eastern')
-
-Convert the datetime:
-
- >>> dt.astimezone(eastern)
- datetime.datetime(2015, 4, 10, 1, 22, tzinfo=zoneinfo.ZoneInfo(key='US/Eastern'))
-
-If you just want the name of the local timezone, use `get_localzone_name()`:
-
- >>> from tzlocal import get_localzone_name
- >>> get_localzone_name()
- "Europe/Warsaw"
-
-Please note that under Unix, `get_localzone_name()` may fail if there is no zone
-configured, where `get_localzone()` would generally succeed.
-
-Troubleshooting
----------------
-
-If you don't get the result you expect, try running it with debugging turned on.
-Start a python interpreter that has tzlocal installed, and run the following code::
-
- import logging
- logging.basicConfig(level="DEBUG")
- import tzlocal
- tzlocal.get_localzone()
-
-The output should look something like this, and this will tell you what
-configurations were found::
-
- DEBUG:root:/etc/timezone found, contents:
- Europe/Warsaw
-
- DEBUG:root:/etc/localtime found
- DEBUG:root:2 found:
- {'/etc/timezone': 'Europe/Warsaw', '/etc/localtime is a symlink to': 'Europe/Warsaw'}
- zoneinfo.ZoneInfo(key='Europe/Warsaw')
-
-
-Development
------------
-
-For ease of development, there is a Makefile that will help you with basic tasks,
-like creating a development environment with all the necessary tools (although
-you need a supported Python version installed first)::
-
- $ make devenv
-
-To run tests::
-
- $ make test
-
-Check the syntax::
-
- $ make check
-
-
-Maintainer
-----------
-
-* Lennart Regebro, [email protected]
-
-Contributors
-------------
-
-* Marc Van Olmen
-* Benjamen Meyer
-* Manuel Ebert
-* Xiaokun Zhu
-* Cameris
-* Edward Betts
-* McK KIM
-* Cris Ewing
-* Ayala Shachar
-* Lev Maximov
-* Jakub Wilk
-* John Quarles
-* Preston Landers
-* Victor Torres
-* Jean Jordaan
-* Zackary Welch
-* Mickaël Schoentgen
-* Gabriel Corona
-* Alex Grönholm
-* Julin S
-* Miroslav Šedivý
-* revansSZ
-* Sam Treweek
-* Peter Di Pasquale
-* Rongrong
-
-(Sorry if I forgot someone)
-
-License
--------
-
-* MIT https://opensource.org/licenses/MIT
diff --git a/contrib/python/tzlocal/py3/tzlocal/__init__.py b/contrib/python/tzlocal/py3/tzlocal/__init__.py
deleted file mode 100644
index 8296a15dc46..00000000000
--- a/contrib/python/tzlocal/py3/tzlocal/__init__.py
+++ /dev/null
@@ -1,19 +0,0 @@
-import sys
-
-if sys.platform == "win32":
- from tzlocal.win32 import (
- get_localzone,
- get_localzone_name,
- reload_localzone,
- )
-else:
- from tzlocal.unix import get_localzone, get_localzone_name, reload_localzone
-
-from tzlocal.utils import assert_tz_offset
-
-__all__ = [
- "get_localzone",
- "get_localzone_name",
- "reload_localzone",
- "assert_tz_offset",
-]
diff --git a/contrib/python/tzlocal/py3/tzlocal/unix.py b/contrib/python/tzlocal/py3/tzlocal/unix.py
deleted file mode 100644
index 117c18d373b..00000000000
--- a/contrib/python/tzlocal/py3/tzlocal/unix.py
+++ /dev/null
@@ -1,231 +0,0 @@
-import logging
-import os
-import re
-import sys
-import warnings
-from datetime import timezone
-
-from tzlocal import utils
-
-if sys.version_info >= (3, 9):
- import zoneinfo # pragma: no cover
-else:
- from backports import zoneinfo # pragma: no cover
-
-_cache_tz = None
-_cache_tz_name = None
-
-log = logging.getLogger("tzlocal")
-
-
-def _get_localzone_name(_root="/"):
- """Tries to find the local timezone configuration.
-
- This method finds the timezone name, if it can, or it returns None.
-
- The parameter _root makes the function look for files like /etc/localtime
- beneath the _root directory. This is primarily used by the tests.
- In normal usage you call the function without parameters."""
-
- # First try the ENV setting.
- tzenv = utils._tz_name_from_env()
- if tzenv:
- return tzenv
-
- # Are we under Termux on Android?
- if os.path.exists(os.path.join(_root, "system/bin/getprop")):
- log.debug("This looks like Termux")
-
- import subprocess
-
- try:
- androidtz = (
- subprocess.check_output(["getprop", "persist.sys.timezone"])
- .strip()
- .decode()
- )
- return androidtz
- except (OSError, subprocess.CalledProcessError):
- # proot environment or failed to getprop
- log.debug("It's not termux?")
- pass
-
- # Now look for distribution specific configuration files
- # that contain the timezone name.
-
- # Stick all of them in a dict, to compare later.
- found_configs = {}
-
- for configfile in ("etc/timezone", "var/db/zoneinfo"):
- tzpath = os.path.join(_root, configfile)
- try:
- with open(tzpath) as tzfile:
- data = tzfile.read()
- log.debug(f"{tzpath} found, contents:\n {data}")
-
- etctz = data.strip("/ \t\r\n")
- if not etctz:
- # Empty file, skip
- continue
- for etctz in etctz.splitlines():
- # Get rid of host definitions and comments:
- if " " in etctz:
- etctz, dummy = etctz.split(" ", 1)
- if "#" in etctz:
- etctz, dummy = etctz.split("#", 1)
- if not etctz:
- continue
-
- found_configs[tzpath] = etctz.replace(" ", "_")
-
- except (OSError, UnicodeDecodeError):
- # File doesn't exist or is a directory, or it's a binary file.
- continue
-
- # CentOS has a ZONE setting in /etc/sysconfig/clock,
- # OpenSUSE has a TIMEZONE setting in /etc/sysconfig/clock and
- # Gentoo has a TIMEZONE setting in /etc/conf.d/clock
- # We look through these files for a timezone:
-
- zone_re = re.compile(r"\s*ZONE\s*=\s*\"")
- timezone_re = re.compile(r"\s*TIMEZONE\s*=\s*\"")
- end_re = re.compile('"')
-
- for filename in ("etc/sysconfig/clock", "etc/conf.d/clock"):
- tzpath = os.path.join(_root, filename)
- try:
- with open(tzpath, "rt") as tzfile:
- data = tzfile.readlines()
- log.debug(f"{tzpath} found, contents:\n {data}")
-
- for line in data:
- # Look for the ZONE= setting.
- match = zone_re.match(line)
- if match is None:
- # No ZONE= setting. Look for the TIMEZONE= setting.
- match = timezone_re.match(line)
- if match is not None:
- # Some setting existed
- line = line[match.end() :]
- etctz = line[: end_re.search(line).start()]
-
- # We found a timezone
- found_configs[tzpath] = etctz.replace(" ", "_")
-
- except (OSError, UnicodeDecodeError):
- # UnicodeDecode handles when clock is symlink to /etc/localtime
- continue
-
- # systemd distributions use symlinks that include the zone name,
- # see manpage of localtime(5) and timedatectl(1)
- tzpath = os.path.join(_root, "etc/localtime")
- if os.path.exists(tzpath) and os.path.islink(tzpath):
- log.debug(f"{tzpath} found")
- etctz = os.path.realpath(tzpath)
- start = etctz.find("/") + 1
- while start != 0:
- etctz = etctz[start:]
- try:
- zoneinfo.ZoneInfo(etctz)
- tzinfo = f"{tzpath} is a symlink to"
- found_configs[tzinfo] = etctz.replace(" ", "_")
- # Only need first valid relative path in simlink.
- break
- except zoneinfo.ZoneInfoNotFoundError:
- pass
- start = etctz.find("/") + 1
-
- if len(found_configs) > 0:
- log.debug(f"{len(found_configs)} found:\n {found_configs}")
- # We found some explicit config of some sort!
- if len(found_configs) > 1:
- # Uh-oh, multiple configs. See if they match:
- unique_tzs = set()
- zoneinfopath = os.path.join(_root, "usr", "share", "zoneinfo")
- directory_depth = len(zoneinfopath.split(os.path.sep))
-
- for tzname in found_configs.values():
- # Look them up in /usr/share/zoneinfo, and find what they
- # really point to:
- path = os.path.realpath(os.path.join(zoneinfopath, *tzname.split("/")))
- real_zone_name = "/".join(path.split(os.path.sep)[directory_depth:])
- unique_tzs.add(real_zone_name)
-
- if len(unique_tzs) != 1:
- message = "Multiple conflicting time zone configurations found:\n"
- for key, value in found_configs.items():
- message += f"{key}: {value}\n"
- message += "Fix the configuration, or set the time zone in a TZ environment variable.\n"
- raise zoneinfo.ZoneInfoNotFoundError(message)
-
- # We found exactly one config! Use it.
- return list(found_configs.values())[0]
-
-
-def _get_localzone(_root="/"):
- """Creates a timezone object from the timezone name.
-
- If there is no timezone config, it will try to create a file from the
- localtime timezone, and if there isn't one, it will default to UTC.
-
- The parameter _root makes the function look for files like /etc/localtime
- beneath the _root directory. This is primarily used by the tests.
- In normal usage you call the function without parameters."""
-
- # First try the ENV setting.
- tzenv = utils._tz_from_env()
- if tzenv:
- return tzenv
-
- tzname = _get_localzone_name(_root)
- if tzname is None:
- # No explicit setting existed. Use localtime
- log.debug("No explicit setting existed. Use localtime")
- for filename in ("etc/localtime", "usr/local/etc/localtime"):
- tzpath = os.path.join(_root, filename)
-
- if not os.path.exists(tzpath):
- continue
- with open(tzpath, "rb") as tzfile:
- tz = zoneinfo.ZoneInfo.from_file(tzfile, key="local")
- break
- else:
- warnings.warn("Can not find any timezone configuration, defaulting to UTC.")
- tz = timezone.utc
- else:
- tz = zoneinfo.ZoneInfo(tzname)
-
- if _root == "/":
- # We are using a file in etc to name the timezone.
- # Verify that the timezone specified there is actually used:
- utils.assert_tz_offset(tz, error=False)
- return tz
-
-
-def get_localzone_name() -> str:
- """Get the computers configured local timezone name, if any."""
- global _cache_tz_name
- if _cache_tz_name is None:
- _cache_tz_name = _get_localzone_name()
-
- return _cache_tz_name
-
-
-def get_localzone() -> zoneinfo.ZoneInfo:
- """Get the computers configured local timezone, if any."""
-
- global _cache_tz
- if _cache_tz is None:
- _cache_tz = _get_localzone()
-
- return _cache_tz
-
-
-def reload_localzone() -> zoneinfo.ZoneInfo:
- """Reload the cached localzone. You need to call this if the timezone has changed."""
- global _cache_tz_name
- global _cache_tz
- _cache_tz_name = _get_localzone_name()
- _cache_tz = _get_localzone()
-
- return _cache_tz
diff --git a/contrib/python/tzlocal/py3/tzlocal/utils.py b/contrib/python/tzlocal/py3/tzlocal/utils.py
deleted file mode 100644
index 3990535f17b..00000000000
--- a/contrib/python/tzlocal/py3/tzlocal/utils.py
+++ /dev/null
@@ -1,112 +0,0 @@
-import calendar
-import datetime
-import logging
-import os
-import time
-import warnings
-
-try:
- import zoneinfo # pragma: no cover
-except ImportError:
- from backports import zoneinfo # pragma: no cover
-
-from tzlocal import windows_tz
-
-log = logging.getLogger("tzlocal")
-
-
-def get_tz_offset(tz):
- """Get timezone's offset using built-in function datetime.utcoffset()."""
- return int(datetime.datetime.now(tz).utcoffset().total_seconds())
-
-
-def assert_tz_offset(tz, error=True):
- """Assert that system's timezone offset equals to the timezone offset found.
-
- If they don't match, we probably have a misconfiguration, for example, an
- incorrect timezone set in /etc/timezone file in systemd distributions.
-
- If error is True, this method will raise a ValueError, otherwise it will
- emit a warning.
- """
-
- tz_offset = get_tz_offset(tz)
- system_offset = calendar.timegm(time.localtime()) - calendar.timegm(time.gmtime())
- # No one has timezone offsets less than a minute, so this should be close enough:
- if abs(tz_offset - system_offset) > 60:
- msg = (
- f"Timezone offset does not match system offset: {tz_offset} != {system_offset}. "
- "Please, check your config files."
- )
- if error:
- raise ValueError(msg)
- warnings.warn(msg)
-
-
-def _tz_name_from_env(tzenv=None):
- if tzenv is None:
- tzenv = os.environ.get("TZ")
-
- if not tzenv:
- return None
-
- log.debug(f"Found a TZ environment: {tzenv}")
-
- if tzenv[0] == ":":
- tzenv = tzenv[1:]
-
- if tzenv in windows_tz.tz_win:
- # Yup, it's a timezone
- return tzenv
-
- if os.path.isabs(tzenv) and os.path.exists(tzenv):
- # It's a file specification, expand it, if possible
- parts = os.path.realpath(tzenv).split(os.sep)
-
- # Is it a zone info zone?
- possible_tz = "/".join(parts[-2:])
- if possible_tz in windows_tz.tz_win:
- # Yup, it is
- return possible_tz
-
- # Maybe it's a short one, like UTC?
- if parts[-1] in windows_tz.tz_win:
- # Indeed
- return parts[-1]
-
- log.debug("TZ does not contain a time zone name")
- return None
-
-
-def _tz_from_env(tzenv=None):
- if tzenv is None:
- tzenv = os.environ.get("TZ")
-
- if not tzenv:
- return None
-
- # Some weird format that exists:
- if tzenv[0] == ":":
- tzenv = tzenv[1:]
-
- # TZ specifies a file
- if os.path.isabs(tzenv) and os.path.exists(tzenv):
- # Try to see if we can figure out the name
- tzname = _tz_name_from_env(tzenv)
- if not tzname:
- # Nope, not a standard timezone name, just take the filename
- tzname = tzenv.split(os.sep)[-1]
- with open(tzenv, "rb") as tzfile:
- return zoneinfo.ZoneInfo.from_file(tzfile, key=tzname)
-
- # TZ must specify a zoneinfo zone.
- try:
- tz = zoneinfo.ZoneInfo(tzenv)
- # That worked, so we return this:
- return tz
- except zoneinfo.ZoneInfoNotFoundError:
- # Nope, it's something like "PST4DST" etc, we can't handle that.
- raise zoneinfo.ZoneInfoNotFoundError(
- f"tzlocal() does not support non-zoneinfo timezones like {tzenv}. \n"
- "Please use a timezone in the form of Continent/City"
- ) from None
diff --git a/contrib/python/tzlocal/py3/tzlocal/win32.py b/contrib/python/tzlocal/py3/tzlocal/win32.py
deleted file mode 100644
index 2fa59fe6ca9..00000000000
--- a/contrib/python/tzlocal/py3/tzlocal/win32.py
+++ /dev/null
@@ -1,147 +0,0 @@
-import logging
-from datetime import datetime
-
-try:
- import _winreg as winreg
-except ImportError:
- import winreg
-
-try:
- import zoneinfo # pragma: no cover
-except ImportError:
- from backports import zoneinfo # pragma: no cover
-
-from tzlocal import utils
-from tzlocal.windows_tz import win_tz
-
-_cache_tz = None
-_cache_tz_name = None
-
-log = logging.getLogger("tzlocal")
-
-
-def valuestodict(key):
- """Convert a registry key's values to a dictionary."""
- result = {}
- size = winreg.QueryInfoKey(key)[1]
- for i in range(size):
- data = winreg.EnumValue(key, i)
- result[data[0]] = data[1]
- return result
-
-
-def _get_dst_info(tz):
- # Find the offset for when it doesn't have DST:
- dst_offset = std_offset = None
- has_dst = False
- year = datetime.now().year
- for dt in (datetime(year, 1, 1), datetime(year, 6, 1)):
- if tz.dst(dt).total_seconds() == 0.0:
- # OK, no DST during winter, get this offset
- std_offset = tz.utcoffset(dt).total_seconds()
- else:
- has_dst = True
-
- return has_dst, std_offset, dst_offset
-
-
-def _get_localzone_name():
- # Windows is special. It has unique time zone names (in several
- # meanings of the word) available, but unfortunately, they can be
- # translated to the language of the operating system, so we need to
- # do a backwards lookup, by going through all time zones and see which
- # one matches.
- tzenv = utils._tz_name_from_env()
- if tzenv:
- return tzenv
-
- log.debug("Looking up time zone info from registry")
- handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
-
- TZLOCALKEYNAME = r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation"
- localtz = winreg.OpenKey(handle, TZLOCALKEYNAME)
- keyvalues = valuestodict(localtz)
- localtz.Close()
-
- if "TimeZoneKeyName" in keyvalues:
- # Windows 7 and later
-
- # For some reason this returns a string with loads of NUL bytes at
- # least on some systems. I don't know if this is a bug somewhere, I
- # just work around it.
- tzkeyname = keyvalues["TimeZoneKeyName"].split("\x00", 1)[0]
- else:
- # Don't support XP any longer
- raise LookupError("Can not find Windows timezone configuration")
-
- timezone = win_tz.get(tzkeyname)
- if timezone is None:
- # Nope, that didn't work. Try adding "Standard Time",
- # it seems to work a lot of times:
- timezone = win_tz.get(tzkeyname + " Standard Time")
-
- # Return what we have.
- if timezone is None:
- raise zoneinfo.ZoneInfoNotFoundError(tzkeyname)
-
- if keyvalues.get("DynamicDaylightTimeDisabled", 0) == 1:
- # DST is disabled, so don't return the timezone name,
- # instead return Etc/GMT+offset
-
- tz = zoneinfo.ZoneInfo(timezone)
- has_dst, std_offset, dst_offset = _get_dst_info(tz)
- if not has_dst:
- # The DST is turned off in the windows configuration,
- # but this timezone doesn't have DST so it doesn't matter
- return timezone
-
- if std_offset is None:
- raise zoneinfo.ZoneInfoNotFoundError(
- f"{tzkeyname} claims to not have a non-DST time!?"
- )
-
- if std_offset % 3600:
- # I can't convert this to an hourly offset
- raise zoneinfo.ZoneInfoNotFoundError(
- f"tzlocal can't support disabling DST in the {timezone} zone."
- )
-
- # This has whole hours as offset, return it as Etc/GMT
- return f"Etc/GMT{-std_offset//3600:+.0f}"
-
- return timezone
-
-
-def get_localzone_name() -> str:
- """Get the zoneinfo timezone name that matches the Windows-configured timezone."""
- global _cache_tz_name
- if _cache_tz_name is None:
- _cache_tz_name = _get_localzone_name()
-
- return _cache_tz_name
-
-
-def get_localzone() -> zoneinfo.ZoneInfo:
- """Returns the zoneinfo-based tzinfo object that matches the Windows-configured timezone."""
-
- global _cache_tz
- if _cache_tz is None:
- _cache_tz = zoneinfo.ZoneInfo(get_localzone_name())
-
- if not utils._tz_name_from_env():
- # If the timezone does NOT come from a TZ environment variable,
- # verify that it's correct. If it's from the environment,
- # we accept it, this is so you can run tests with different timezones.
- utils.assert_tz_offset(_cache_tz, error=False)
-
- return _cache_tz
-
-
-def reload_localzone() -> zoneinfo.ZoneInfo:
- """Reload the cached localzone. You need to call this if the timezone has changed."""
- global _cache_tz
- global _cache_tz_name
- _cache_tz_name = _get_localzone_name()
- _cache_tz = zoneinfo.ZoneInfo(_cache_tz_name)
- utils.assert_tz_offset(_cache_tz, error=False)
- return _cache_tz
diff --git a/contrib/python/tzlocal/py3/tzlocal/windows_tz.py b/contrib/python/tzlocal/py3/tzlocal/windows_tz.py
deleted file mode 100644
index 3d47576086d..00000000000
--- a/contrib/python/tzlocal/py3/tzlocal/windows_tz.py
+++ /dev/null
@@ -1,736 +0,0 @@
-# This file is autogenerated by the update_windows_mapping.py script
-# Do not edit.
-win_tz = {
- "AUS Central Standard Time": "Australia/Darwin",
- "AUS Eastern Standard Time": "Australia/Sydney",
- "Afghanistan Standard Time": "Asia/Kabul",
- "Alaskan Standard Time": "America/Anchorage",
- "Aleutian Standard Time": "America/Adak",
- "Altai Standard Time": "Asia/Barnaul",
- "Arab Standard Time": "Asia/Riyadh",
- "Arabian Standard Time": "Asia/Dubai",
- "Arabic Standard Time": "Asia/Baghdad",
- "Argentina Standard Time": "America/Buenos_Aires",
- "Astrakhan Standard Time": "Europe/Astrakhan",
- "Atlantic Standard Time": "America/Halifax",
- "Aus Central W. Standard Time": "Australia/Eucla",
- "Azerbaijan Standard Time": "Asia/Baku",
- "Azores Standard Time": "Atlantic/Azores",
- "Bahia Standard Time": "America/Bahia",
- "Bangladesh Standard Time": "Asia/Dhaka",
- "Belarus Standard Time": "Europe/Minsk",
- "Bougainville Standard Time": "Pacific/Bougainville",
- "Canada Central Standard Time": "America/Regina",
- "Cape Verde Standard Time": "Atlantic/Cape_Verde",
- "Caucasus Standard Time": "Asia/Yerevan",
- "Cen. Australia Standard Time": "Australia/Adelaide",
- "Central America Standard Time": "America/Guatemala",
- "Central Asia Standard Time": "Asia/Almaty",
- "Central Brazilian Standard Time": "America/Cuiaba",
- "Central Europe Standard Time": "Europe/Budapest",
- "Central European Standard Time": "Europe/Warsaw",
- "Central Pacific Standard Time": "Pacific/Guadalcanal",
- "Central Standard Time": "America/Chicago",
- "Central Standard Time (Mexico)": "America/Mexico_City",
- "Chatham Islands Standard Time": "Pacific/Chatham",
- "China Standard Time": "Asia/Shanghai",
- "Cuba Standard Time": "America/Havana",
- "Dateline Standard Time": "Etc/GMT+12",
- "E. Africa Standard Time": "Africa/Nairobi",
- "E. Australia Standard Time": "Australia/Brisbane",
- "E. Europe Standard Time": "Europe/Chisinau",
- "E. South America Standard Time": "America/Sao_Paulo",
- "Easter Island Standard Time": "Pacific/Easter",
- "Eastern Standard Time": "America/New_York",
- "Eastern Standard Time (Mexico)": "America/Cancun",
- "Egypt Standard Time": "Africa/Cairo",
- "Ekaterinburg Standard Time": "Asia/Yekaterinburg",
- "FLE Standard Time": "Europe/Kiev",
- "Fiji Standard Time": "Pacific/Fiji",
- "GMT Standard Time": "Europe/London",
- "GTB Standard Time": "Europe/Bucharest",
- "Georgian Standard Time": "Asia/Tbilisi",
- "Greenland Standard Time": "America/Godthab",
- "Greenwich Standard Time": "Atlantic/Reykjavik",
- "Haiti Standard Time": "America/Port-au-Prince",
- "Hawaiian Standard Time": "Pacific/Honolulu",
- "India Standard Time": "Asia/Calcutta",
- "Iran Standard Time": "Asia/Tehran",
- "Israel Standard Time": "Asia/Jerusalem",
- "Jordan Standard Time": "Asia/Amman",
- "Kaliningrad Standard Time": "Europe/Kaliningrad",
- "Korea Standard Time": "Asia/Seoul",
- "Libya Standard Time": "Africa/Tripoli",
- "Line Islands Standard Time": "Pacific/Kiritimati",
- "Lord Howe Standard Time": "Australia/Lord_Howe",
- "Magadan Standard Time": "Asia/Magadan",
- "Magallanes Standard Time": "America/Punta_Arenas",
- "Marquesas Standard Time": "Pacific/Marquesas",
- "Mauritius Standard Time": "Indian/Mauritius",
- "Middle East Standard Time": "Asia/Beirut",
- "Montevideo Standard Time": "America/Montevideo",
- "Morocco Standard Time": "Africa/Casablanca",
- "Mountain Standard Time": "America/Denver",
- "Mountain Standard Time (Mexico)": "America/Mazatlan",
- "Myanmar Standard Time": "Asia/Rangoon",
- "N. Central Asia Standard Time": "Asia/Novosibirsk",
- "Namibia Standard Time": "Africa/Windhoek",
- "Nepal Standard Time": "Asia/Katmandu",
- "New Zealand Standard Time": "Pacific/Auckland",
- "Newfoundland Standard Time": "America/St_Johns",
- "Norfolk Standard Time": "Pacific/Norfolk",
- "North Asia East Standard Time": "Asia/Irkutsk",
- "North Asia Standard Time": "Asia/Krasnoyarsk",
- "North Korea Standard Time": "Asia/Pyongyang",
- "Omsk Standard Time": "Asia/Omsk",
- "Pacific SA Standard Time": "America/Santiago",
- "Pacific Standard Time": "America/Los_Angeles",
- "Pacific Standard Time (Mexico)": "America/Tijuana",
- "Pakistan Standard Time": "Asia/Karachi",
- "Paraguay Standard Time": "America/Asuncion",
- "Qyzylorda Standard Time": "Asia/Qyzylorda",
- "Romance Standard Time": "Europe/Paris",
- "Russia Time Zone 10": "Asia/Srednekolymsk",
- "Russia Time Zone 11": "Asia/Kamchatka",
- "Russia Time Zone 3": "Europe/Samara",
- "Russian Standard Time": "Europe/Moscow",
- "SA Eastern Standard Time": "America/Cayenne",
- "SA Pacific Standard Time": "America/Bogota",
- "SA Western Standard Time": "America/La_Paz",
- "SE Asia Standard Time": "Asia/Bangkok",
- "Saint Pierre Standard Time": "America/Miquelon",
- "Sakhalin Standard Time": "Asia/Sakhalin",
- "Samoa Standard Time": "Pacific/Apia",
- "Sao Tome Standard Time": "Africa/Sao_Tome",
- "Saratov Standard Time": "Europe/Saratov",
- "Singapore Standard Time": "Asia/Singapore",
- "South Africa Standard Time": "Africa/Johannesburg",
- "South Sudan Standard Time": "Africa/Juba",
- "Sri Lanka Standard Time": "Asia/Colombo",
- "Sudan Standard Time": "Africa/Khartoum",
- "Syria Standard Time": "Asia/Damascus",
- "Taipei Standard Time": "Asia/Taipei",
- "Tasmania Standard Time": "Australia/Hobart",
- "Tocantins Standard Time": "America/Araguaina",
- "Tokyo Standard Time": "Asia/Tokyo",
- "Tomsk Standard Time": "Asia/Tomsk",
- "Tonga Standard Time": "Pacific/Tongatapu",
- "Transbaikal Standard Time": "Asia/Chita",
- "Turkey Standard Time": "Europe/Istanbul",
- "Turks And Caicos Standard Time": "America/Grand_Turk",
- "US Eastern Standard Time": "America/Indianapolis",
- "US Mountain Standard Time": "America/Phoenix",
- "UTC": "Etc/UTC",
- "UTC+12": "Etc/GMT-12",
- "UTC+13": "Etc/GMT-13",
- "UTC-02": "Etc/GMT+2",
- "UTC-08": "Etc/GMT+8",
- "UTC-09": "Etc/GMT+9",
- "UTC-11": "Etc/GMT+11",
- "Ulaanbaatar Standard Time": "Asia/Ulaanbaatar",
- "Venezuela Standard Time": "America/Caracas",
- "Vladivostok Standard Time": "Asia/Vladivostok",
- "Volgograd Standard Time": "Europe/Volgograd",
- "W. Australia Standard Time": "Australia/Perth",
- "W. Central Africa Standard Time": "Africa/Lagos",
- "W. Europe Standard Time": "Europe/Berlin",
- "W. Mongolia Standard Time": "Asia/Hovd",
- "West Asia Standard Time": "Asia/Tashkent",
- "West Bank Standard Time": "Asia/Hebron",
- "West Pacific Standard Time": "Pacific/Port_Moresby",
- "Yakutsk Standard Time": "Asia/Yakutsk",
- "Yukon Standard Time": "America/Whitehorse",
-}
-
-# Old name for the win_tz variable:
-tz_names = win_tz
-
-tz_win = {
- "": "Central Standard Time (Mexico)",
- "Africa/Abidjan": "Greenwich Standard Time",
- "Africa/Accra": "Greenwich Standard Time",
- "Africa/Addis_Ababa": "E. Africa Standard Time",
- "Africa/Algiers": "W. Central Africa Standard Time",
- "Africa/Asmara": "E. Africa Standard Time",
- "Africa/Asmera": "E. Africa Standard Time",
- "Africa/Bamako": "Greenwich Standard Time",
- "Africa/Bangui": "W. Central Africa Standard Time",
- "Africa/Banjul": "Greenwich Standard Time",
- "Africa/Bissau": "Greenwich Standard Time",
- "Africa/Blantyre": "South Africa Standard Time",
- "Africa/Brazzaville": "W. Central Africa Standard Time",
- "Africa/Bujumbura": "South Africa Standard Time",
- "Africa/Cairo": "Egypt Standard Time",
- "Africa/Casablanca": "Morocco Standard Time",
- "Africa/Ceuta": "Romance Standard Time",
- "Africa/Conakry": "Greenwich Standard Time",
- "Africa/Dakar": "Greenwich Standard Time",
- "Africa/Dar_es_Salaam": "E. Africa Standard Time",
- "Africa/Djibouti": "E. Africa Standard Time",
- "Africa/Douala": "W. Central Africa Standard Time",
- "Africa/El_Aaiun": "Morocco Standard Time",
- "Africa/Freetown": "Greenwich Standard Time",
- "Africa/Gaborone": "South Africa Standard Time",
- "Africa/Harare": "South Africa Standard Time",
- "Africa/Johannesburg": "South Africa Standard Time",
- "Africa/Juba": "South Sudan Standard Time",
- "Africa/Kampala": "E. Africa Standard Time",
- "Africa/Khartoum": "Sudan Standard Time",
- "Africa/Kigali": "South Africa Standard Time",
- "Africa/Kinshasa": "W. Central Africa Standard Time",
- "Africa/Lagos": "W. Central Africa Standard Time",
- "Africa/Libreville": "W. Central Africa Standard Time",
- "Africa/Lome": "Greenwich Standard Time",
- "Africa/Luanda": "W. Central Africa Standard Time",
- "Africa/Lubumbashi": "South Africa Standard Time",
- "Africa/Lusaka": "South Africa Standard Time",
- "Africa/Malabo": "W. Central Africa Standard Time",
- "Africa/Maputo": "South Africa Standard Time",
- "Africa/Maseru": "South Africa Standard Time",
- "Africa/Mbabane": "South Africa Standard Time",
- "Africa/Mogadishu": "E. Africa Standard Time",
- "Africa/Monrovia": "Greenwich Standard Time",
- "Africa/Nairobi": "E. Africa Standard Time",
- "Africa/Ndjamena": "W. Central Africa Standard Time",
- "Africa/Niamey": "W. Central Africa Standard Time",
- "Africa/Nouakchott": "Greenwich Standard Time",
- "Africa/Ouagadougou": "Greenwich Standard Time",
- "Africa/Porto-Novo": "W. Central Africa Standard Time",
- "Africa/Sao_Tome": "Sao Tome Standard Time",
- "Africa/Timbuktu": "Greenwich Standard Time",
- "Africa/Tripoli": "Libya Standard Time",
- "Africa/Tunis": "W. Central Africa Standard Time",
- "Africa/Windhoek": "Namibia Standard Time",
- "America/Adak": "Aleutian Standard Time",
- "America/Anchorage": "Alaskan Standard Time",
- "America/Anguilla": "SA Western Standard Time",
- "America/Antigua": "SA Western Standard Time",
- "America/Araguaina": "Tocantins Standard Time",
- "America/Argentina/Buenos_Aires": "Argentina Standard Time",
- "America/Argentina/Catamarca": "Argentina Standard Time",
- "America/Argentina/ComodRivadavia": "Argentina Standard Time",
- "America/Argentina/Cordoba": "Argentina Standard Time",
- "America/Argentina/Jujuy": "Argentina Standard Time",
- "America/Argentina/La_Rioja": "Argentina Standard Time",
- "America/Argentina/Mendoza": "Argentina Standard Time",
- "America/Argentina/Rio_Gallegos": "Argentina Standard Time",
- "America/Argentina/Salta": "Argentina Standard Time",
- "America/Argentina/San_Juan": "Argentina Standard Time",
- "America/Argentina/San_Luis": "Argentina Standard Time",
- "America/Argentina/Tucuman": "Argentina Standard Time",
- "America/Argentina/Ushuaia": "Argentina Standard Time",
- "America/Aruba": "SA Western Standard Time",
- "America/Asuncion": "Paraguay Standard Time",
- "America/Atikokan": "SA Pacific Standard Time",
- "America/Atka": "Aleutian Standard Time",
- "America/Bahia": "Bahia Standard Time",
- "America/Bahia_Banderas": "Central Standard Time (Mexico)",
- "America/Barbados": "SA Western Standard Time",
- "America/Belem": "SA Eastern Standard Time",
- "America/Belize": "Central America Standard Time",
- "America/Blanc-Sablon": "SA Western Standard Time",
- "America/Boa_Vista": "SA Western Standard Time",
- "America/Bogota": "SA Pacific Standard Time",
- "America/Boise": "Mountain Standard Time",
- "America/Buenos_Aires": "Argentina Standard Time",
- "America/Cambridge_Bay": "Mountain Standard Time",
- "America/Campo_Grande": "Central Brazilian Standard Time",
- "America/Cancun": "Eastern Standard Time (Mexico)",
- "America/Caracas": "Venezuela Standard Time",
- "America/Catamarca": "Argentina Standard Time",
- "America/Cayenne": "SA Eastern Standard Time",
- "America/Cayman": "SA Pacific Standard Time",
- "America/Chicago": "Central Standard Time",
- "America/Chihuahua": "Central Standard Time (Mexico)",
- "America/Ciudad_Juarez": "Mountain Standard Time",
- "America/Coral_Harbour": "SA Pacific Standard Time",
- "America/Cordoba": "Argentina Standard Time",
- "America/Costa_Rica": "Central America Standard Time",
- "America/Creston": "US Mountain Standard Time",
- "America/Cuiaba": "Central Brazilian Standard Time",
- "America/Curacao": "SA Western Standard Time",
- "America/Danmarkshavn": "Greenwich Standard Time",
- "America/Dawson": "Yukon Standard Time",
- "America/Dawson_Creek": "US Mountain Standard Time",
- "America/Denver": "Mountain Standard Time",
- "America/Detroit": "Eastern Standard Time",
- "America/Dominica": "SA Western Standard Time",
- "America/Edmonton": "Mountain Standard Time",
- "America/Eirunepe": "SA Pacific Standard Time",
- "America/El_Salvador": "Central America Standard Time",
- "America/Ensenada": "Pacific Standard Time (Mexico)",
- "America/Fort_Nelson": "US Mountain Standard Time",
- "America/Fort_Wayne": "US Eastern Standard Time",
- "America/Fortaleza": "SA Eastern Standard Time",
- "America/Glace_Bay": "Atlantic Standard Time",
- "America/Godthab": "Greenland Standard Time",
- "America/Goose_Bay": "Atlantic Standard Time",
- "America/Grand_Turk": "Turks And Caicos Standard Time",
- "America/Grenada": "SA Western Standard Time",
- "America/Guadeloupe": "SA Western Standard Time",
- "America/Guatemala": "Central America Standard Time",
- "America/Guayaquil": "SA Pacific Standard Time",
- "America/Guyana": "SA Western Standard Time",
- "America/Halifax": "Atlantic Standard Time",
- "America/Havana": "Cuba Standard Time",
- "America/Hermosillo": "US Mountain Standard Time",
- "America/Indiana/Indianapolis": "US Eastern Standard Time",
- "America/Indiana/Knox": "Central Standard Time",
- "America/Indiana/Marengo": "US Eastern Standard Time",
- "America/Indiana/Petersburg": "Eastern Standard Time",
- "America/Indiana/Tell_City": "Central Standard Time",
- "America/Indiana/Vevay": "US Eastern Standard Time",
- "America/Indiana/Vincennes": "Eastern Standard Time",
- "America/Indiana/Winamac": "Eastern Standard Time",
- "America/Indianapolis": "US Eastern Standard Time",
- "America/Inuvik": "Mountain Standard Time",
- "America/Iqaluit": "Eastern Standard Time",
- "America/Jamaica": "SA Pacific Standard Time",
- "America/Jujuy": "Argentina Standard Time",
- "America/Juneau": "Alaskan Standard Time",
- "America/Kentucky/Louisville": "Eastern Standard Time",
- "America/Kentucky/Monticello": "Eastern Standard Time",
- "America/Knox_IN": "Central Standard Time",
- "America/Kralendijk": "SA Western Standard Time",
- "America/La_Paz": "SA Western Standard Time",
- "America/Lima": "SA Pacific Standard Time",
- "America/Los_Angeles": "Pacific Standard Time",
- "America/Louisville": "Eastern Standard Time",
- "America/Lower_Princes": "SA Western Standard Time",
- "America/Maceio": "SA Eastern Standard Time",
- "America/Managua": "Central America Standard Time",
- "America/Manaus": "SA Western Standard Time",
- "America/Marigot": "SA Western Standard Time",
- "America/Martinique": "SA Western Standard Time",
- "America/Matamoros": "Central Standard Time",
- "America/Mazatlan": "Mountain Standard Time (Mexico)",
- "America/Mendoza": "Argentina Standard Time",
- "America/Menominee": "Central Standard Time",
- "America/Merida": "Central Standard Time (Mexico)",
- "America/Metlakatla": "Alaskan Standard Time",
- "America/Mexico_City": "Central Standard Time (Mexico)",
- "America/Miquelon": "Saint Pierre Standard Time",
- "America/Moncton": "Atlantic Standard Time",
- "America/Monterrey": "Central Standard Time (Mexico)",
- "America/Montevideo": "Montevideo Standard Time",
- "America/Montreal": "Eastern Standard Time",
- "America/Montserrat": "SA Western Standard Time",
- "America/Nassau": "Eastern Standard Time",
- "America/New_York": "Eastern Standard Time",
- "America/Nipigon": "Eastern Standard Time",
- "America/Nome": "Alaskan Standard Time",
- "America/Noronha": "UTC-02",
- "America/North_Dakota/Beulah": "Central Standard Time",
- "America/North_Dakota/Center": "Central Standard Time",
- "America/North_Dakota/New_Salem": "Central Standard Time",
- "America/Nuuk": "Greenland Standard Time",
- "America/Ojinaga": "Central Standard Time",
- "America/Panama": "SA Pacific Standard Time",
- "America/Pangnirtung": "Eastern Standard Time",
- "America/Paramaribo": "SA Eastern Standard Time",
- "America/Phoenix": "US Mountain Standard Time",
- "America/Port-au-Prince": "Haiti Standard Time",
- "America/Port_of_Spain": "SA Western Standard Time",
- "America/Porto_Acre": "SA Pacific Standard Time",
- "America/Porto_Velho": "SA Western Standard Time",
- "America/Puerto_Rico": "SA Western Standard Time",
- "America/Punta_Arenas": "Magallanes Standard Time",
- "America/Rainy_River": "Central Standard Time",
- "America/Rankin_Inlet": "Central Standard Time",
- "America/Recife": "SA Eastern Standard Time",
- "America/Regina": "Canada Central Standard Time",
- "America/Resolute": "Central Standard Time",
- "America/Rio_Branco": "SA Pacific Standard Time",
- "America/Rosario": "Argentina Standard Time",
- "America/Santa_Isabel": "Pacific Standard Time (Mexico)",
- "America/Santarem": "SA Eastern Standard Time",
- "America/Santiago": "Pacific SA Standard Time",
- "America/Santo_Domingo": "SA Western Standard Time",
- "America/Sao_Paulo": "E. South America Standard Time",
- "America/Scoresbysund": "Azores Standard Time",
- "America/Shiprock": "Mountain Standard Time",
- "America/Sitka": "Alaskan Standard Time",
- "America/St_Barthelemy": "SA Western Standard Time",
- "America/St_Johns": "Newfoundland Standard Time",
- "America/St_Kitts": "SA Western Standard Time",
- "America/St_Lucia": "SA Western Standard Time",
- "America/St_Thomas": "SA Western Standard Time",
- "America/St_Vincent": "SA Western Standard Time",
- "America/Swift_Current": "Canada Central Standard Time",
- "America/Tegucigalpa": "Central America Standard Time",
- "America/Thule": "Atlantic Standard Time",
- "America/Thunder_Bay": "Eastern Standard Time",
- "America/Tijuana": "Pacific Standard Time (Mexico)",
- "America/Toronto": "Eastern Standard Time",
- "America/Tortola": "SA Western Standard Time",
- "America/Vancouver": "Pacific Standard Time",
- "America/Virgin": "SA Western Standard Time",
- "America/Whitehorse": "Yukon Standard Time",
- "America/Winnipeg": "Central Standard Time",
- "America/Yakutat": "Alaskan Standard Time",
- "America/Yellowknife": "Mountain Standard Time",
- "Antarctica/Casey": "Central Pacific Standard Time",
- "Antarctica/Davis": "SE Asia Standard Time",
- "Antarctica/DumontDUrville": "West Pacific Standard Time",
- "Antarctica/Macquarie": "Tasmania Standard Time",
- "Antarctica/Mawson": "West Asia Standard Time",
- "Antarctica/McMurdo": "New Zealand Standard Time",
- "Antarctica/Palmer": "SA Eastern Standard Time",
- "Antarctica/Rothera": "SA Eastern Standard Time",
- "Antarctica/South_Pole": "New Zealand Standard Time",
- "Antarctica/Syowa": "E. Africa Standard Time",
- "Antarctica/Vostok": "Central Asia Standard Time",
- "Arctic/Longyearbyen": "W. Europe Standard Time",
- "Asia/Aden": "Arab Standard Time",
- "Asia/Almaty": "Central Asia Standard Time",
- "Asia/Amman": "Jordan Standard Time",
- "Asia/Anadyr": "Russia Time Zone 11",
- "Asia/Aqtau": "West Asia Standard Time",
- "Asia/Aqtobe": "West Asia Standard Time",
- "Asia/Ashgabat": "West Asia Standard Time",
- "Asia/Ashkhabad": "West Asia Standard Time",
- "Asia/Atyrau": "West Asia Standard Time",
- "Asia/Baghdad": "Arabic Standard Time",
- "Asia/Bahrain": "Arab Standard Time",
- "Asia/Baku": "Azerbaijan Standard Time",
- "Asia/Bangkok": "SE Asia Standard Time",
- "Asia/Barnaul": "Altai Standard Time",
- "Asia/Beirut": "Middle East Standard Time",
- "Asia/Bishkek": "Central Asia Standard Time",
- "Asia/Brunei": "Singapore Standard Time",
- "Asia/Calcutta": "India Standard Time",
- "Asia/Chita": "Transbaikal Standard Time",
- "Asia/Choibalsan": "Ulaanbaatar Standard Time",
- "Asia/Chongqing": "China Standard Time",
- "Asia/Chungking": "China Standard Time",
- "Asia/Colombo": "Sri Lanka Standard Time",
- "Asia/Dacca": "Bangladesh Standard Time",
- "Asia/Damascus": "Syria Standard Time",
- "Asia/Dhaka": "Bangladesh Standard Time",
- "Asia/Dili": "Tokyo Standard Time",
- "Asia/Dubai": "Arabian Standard Time",
- "Asia/Dushanbe": "West Asia Standard Time",
- "Asia/Famagusta": "GTB Standard Time",
- "Asia/Gaza": "West Bank Standard Time",
- "Asia/Harbin": "China Standard Time",
- "Asia/Hebron": "West Bank Standard Time",
- "Asia/Ho_Chi_Minh": "SE Asia Standard Time",
- "Asia/Hong_Kong": "China Standard Time",
- "Asia/Hovd": "W. Mongolia Standard Time",
- "Asia/Irkutsk": "North Asia East Standard Time",
- "Asia/Istanbul": "Turkey Standard Time",
- "Asia/Jakarta": "SE Asia Standard Time",
- "Asia/Jayapura": "Tokyo Standard Time",
- "Asia/Jerusalem": "Israel Standard Time",
- "Asia/Kabul": "Afghanistan Standard Time",
- "Asia/Kamchatka": "Russia Time Zone 11",
- "Asia/Karachi": "Pakistan Standard Time",
- "Asia/Kashgar": "Central Asia Standard Time",
- "Asia/Kathmandu": "Nepal Standard Time",
- "Asia/Katmandu": "Nepal Standard Time",
- "Asia/Khandyga": "Yakutsk Standard Time",
- "Asia/Kolkata": "India Standard Time",
- "Asia/Krasnoyarsk": "North Asia Standard Time",
- "Asia/Kuala_Lumpur": "Singapore Standard Time",
- "Asia/Kuching": "Singapore Standard Time",
- "Asia/Kuwait": "Arab Standard Time",
- "Asia/Macao": "China Standard Time",
- "Asia/Macau": "China Standard Time",
- "Asia/Magadan": "Magadan Standard Time",
- "Asia/Makassar": "Singapore Standard Time",
- "Asia/Manila": "Singapore Standard Time",
- "Asia/Muscat": "Arabian Standard Time",
- "Asia/Nicosia": "GTB Standard Time",
- "Asia/Novokuznetsk": "North Asia Standard Time",
- "Asia/Novosibirsk": "N. Central Asia Standard Time",
- "Asia/Omsk": "Omsk Standard Time",
- "Asia/Oral": "West Asia Standard Time",
- "Asia/Phnom_Penh": "SE Asia Standard Time",
- "Asia/Pontianak": "SE Asia Standard Time",
- "Asia/Pyongyang": "North Korea Standard Time",
- "Asia/Qatar": "Arab Standard Time",
- "Asia/Qostanay": "Central Asia Standard Time",
- "Asia/Qyzylorda": "Qyzylorda Standard Time",
- "Asia/Rangoon": "Myanmar Standard Time",
- "Asia/Riyadh": "Arab Standard Time",
- "Asia/Saigon": "SE Asia Standard Time",
- "Asia/Sakhalin": "Sakhalin Standard Time",
- "Asia/Samarkand": "West Asia Standard Time",
- "Asia/Seoul": "Korea Standard Time",
- "Asia/Shanghai": "China Standard Time",
- "Asia/Singapore": "Singapore Standard Time",
- "Asia/Srednekolymsk": "Russia Time Zone 10",
- "Asia/Taipei": "Taipei Standard Time",
- "Asia/Tashkent": "West Asia Standard Time",
- "Asia/Tbilisi": "Georgian Standard Time",
- "Asia/Tehran": "Iran Standard Time",
- "Asia/Tel_Aviv": "Israel Standard Time",
- "Asia/Thimbu": "Bangladesh Standard Time",
- "Asia/Thimphu": "Bangladesh Standard Time",
- "Asia/Tokyo": "Tokyo Standard Time",
- "Asia/Tomsk": "Tomsk Standard Time",
- "Asia/Ujung_Pandang": "Singapore Standard Time",
- "Asia/Ulaanbaatar": "Ulaanbaatar Standard Time",
- "Asia/Ulan_Bator": "Ulaanbaatar Standard Time",
- "Asia/Urumqi": "Central Asia Standard Time",
- "Asia/Ust-Nera": "Vladivostok Standard Time",
- "Asia/Vientiane": "SE Asia Standard Time",
- "Asia/Vladivostok": "Vladivostok Standard Time",
- "Asia/Yakutsk": "Yakutsk Standard Time",
- "Asia/Yangon": "Myanmar Standard Time",
- "Asia/Yekaterinburg": "Ekaterinburg Standard Time",
- "Asia/Yerevan": "Caucasus Standard Time",
- "Atlantic/Azores": "Azores Standard Time",
- "Atlantic/Bermuda": "Atlantic Standard Time",
- "Atlantic/Canary": "GMT Standard Time",
- "Atlantic/Cape_Verde": "Cape Verde Standard Time",
- "Atlantic/Faeroe": "GMT Standard Time",
- "Atlantic/Faroe": "GMT Standard Time",
- "Atlantic/Jan_Mayen": "W. Europe Standard Time",
- "Atlantic/Madeira": "GMT Standard Time",
- "Atlantic/Reykjavik": "Greenwich Standard Time",
- "Atlantic/South_Georgia": "UTC-02",
- "Atlantic/St_Helena": "Greenwich Standard Time",
- "Atlantic/Stanley": "SA Eastern Standard Time",
- "Australia/ACT": "AUS Eastern Standard Time",
- "Australia/Adelaide": "Cen. Australia Standard Time",
- "Australia/Brisbane": "E. Australia Standard Time",
- "Australia/Broken_Hill": "Cen. Australia Standard Time",
- "Australia/Canberra": "AUS Eastern Standard Time",
- "Australia/Currie": "Tasmania Standard Time",
- "Australia/Darwin": "AUS Central Standard Time",
- "Australia/Eucla": "Aus Central W. Standard Time",
- "Australia/Hobart": "Tasmania Standard Time",
- "Australia/LHI": "Lord Howe Standard Time",
- "Australia/Lindeman": "E. Australia Standard Time",
- "Australia/Lord_Howe": "Lord Howe Standard Time",
- "Australia/Melbourne": "AUS Eastern Standard Time",
- "Australia/NSW": "AUS Eastern Standard Time",
- "Australia/North": "AUS Central Standard Time",
- "Australia/Perth": "W. Australia Standard Time",
- "Australia/Queensland": "E. Australia Standard Time",
- "Australia/South": "Cen. Australia Standard Time",
- "Australia/Sydney": "AUS Eastern Standard Time",
- "Australia/Tasmania": "Tasmania Standard Time",
- "Australia/Victoria": "AUS Eastern Standard Time",
- "Australia/West": "W. Australia Standard Time",
- "Australia/Yancowinna": "Cen. Australia Standard Time",
- "Brazil/Acre": "SA Pacific Standard Time",
- "Brazil/DeNoronha": "UTC-02",
- "Brazil/East": "E. South America Standard Time",
- "Brazil/West": "SA Western Standard Time",
- "CST6CDT": "Central Standard Time",
- "Canada/Atlantic": "Atlantic Standard Time",
- "Canada/Central": "Central Standard Time",
- "Canada/Eastern": "Eastern Standard Time",
- "Canada/Mountain": "Mountain Standard Time",
- "Canada/Newfoundland": "Newfoundland Standard Time",
- "Canada/Pacific": "Pacific Standard Time",
- "Canada/Saskatchewan": "Canada Central Standard Time",
- "Canada/Yukon": "Yukon Standard Time",
- "Chile/Continental": "Pacific SA Standard Time",
- "Chile/EasterIsland": "Easter Island Standard Time",
- "Cuba": "Cuba Standard Time",
- "EST5EDT": "Eastern Standard Time",
- "Egypt": "Egypt Standard Time",
- "Eire": "GMT Standard Time",
- "Etc/GMT": "UTC",
- "Etc/GMT+0": "UTC",
- "Etc/GMT+1": "Cape Verde Standard Time",
- "Etc/GMT+10": "Hawaiian Standard Time",
- "Etc/GMT+11": "UTC-11",
- "Etc/GMT+12": "Dateline Standard Time",
- "Etc/GMT+2": "UTC-02",
- "Etc/GMT+3": "SA Eastern Standard Time",
- "Etc/GMT+4": "SA Western Standard Time",
- "Etc/GMT+5": "SA Pacific Standard Time",
- "Etc/GMT+6": "Central America Standard Time",
- "Etc/GMT+7": "US Mountain Standard Time",
- "Etc/GMT+8": "UTC-08",
- "Etc/GMT+9": "UTC-09",
- "Etc/GMT-0": "UTC",
- "Etc/GMT-1": "W. Central Africa Standard Time",
- "Etc/GMT-10": "West Pacific Standard Time",
- "Etc/GMT-11": "Central Pacific Standard Time",
- "Etc/GMT-12": "UTC+12",
- "Etc/GMT-13": "UTC+13",
- "Etc/GMT-14": "Line Islands Standard Time",
- "Etc/GMT-2": "South Africa Standard Time",
- "Etc/GMT-3": "E. Africa Standard Time",
- "Etc/GMT-4": "Arabian Standard Time",
- "Etc/GMT-5": "West Asia Standard Time",
- "Etc/GMT-6": "Central Asia Standard Time",
- "Etc/GMT-7": "SE Asia Standard Time",
- "Etc/GMT-8": "Singapore Standard Time",
- "Etc/GMT-9": "Tokyo Standard Time",
- "Etc/GMT0": "UTC",
- "Etc/Greenwich": "UTC",
- "Etc/UCT": "UTC",
- "Etc/UTC": "UTC",
- "Etc/Universal": "UTC",
- "Etc/Zulu": "UTC",
- "Europe/Amsterdam": "W. Europe Standard Time",
- "Europe/Andorra": "W. Europe Standard Time",
- "Europe/Astrakhan": "Astrakhan Standard Time",
- "Europe/Athens": "GTB Standard Time",
- "Europe/Belfast": "GMT Standard Time",
- "Europe/Belgrade": "Central Europe Standard Time",
- "Europe/Berlin": "W. Europe Standard Time",
- "Europe/Bratislava": "Central Europe Standard Time",
- "Europe/Brussels": "Romance Standard Time",
- "Europe/Bucharest": "GTB Standard Time",
- "Europe/Budapest": "Central Europe Standard Time",
- "Europe/Busingen": "W. Europe Standard Time",
- "Europe/Chisinau": "E. Europe Standard Time",
- "Europe/Copenhagen": "Romance Standard Time",
- "Europe/Dublin": "GMT Standard Time",
- "Europe/Gibraltar": "W. Europe Standard Time",
- "Europe/Guernsey": "GMT Standard Time",
- "Europe/Helsinki": "FLE Standard Time",
- "Europe/Isle_of_Man": "GMT Standard Time",
- "Europe/Istanbul": "Turkey Standard Time",
- "Europe/Jersey": "GMT Standard Time",
- "Europe/Kaliningrad": "Kaliningrad Standard Time",
- "Europe/Kiev": "FLE Standard Time",
- "Europe/Kirov": "Russian Standard Time",
- "Europe/Kyiv": "FLE Standard Time",
- "Europe/Lisbon": "GMT Standard Time",
- "Europe/Ljubljana": "Central Europe Standard Time",
- "Europe/London": "GMT Standard Time",
- "Europe/Luxembourg": "W. Europe Standard Time",
- "Europe/Madrid": "Romance Standard Time",
- "Europe/Malta": "W. Europe Standard Time",
- "Europe/Mariehamn": "FLE Standard Time",
- "Europe/Minsk": "Belarus Standard Time",
- "Europe/Monaco": "W. Europe Standard Time",
- "Europe/Moscow": "Russian Standard Time",
- "Europe/Nicosia": "GTB Standard Time",
- "Europe/Oslo": "W. Europe Standard Time",
- "Europe/Paris": "Romance Standard Time",
- "Europe/Podgorica": "Central Europe Standard Time",
- "Europe/Prague": "Central Europe Standard Time",
- "Europe/Riga": "FLE Standard Time",
- "Europe/Rome": "W. Europe Standard Time",
- "Europe/Samara": "Russia Time Zone 3",
- "Europe/San_Marino": "W. Europe Standard Time",
- "Europe/Sarajevo": "Central European Standard Time",
- "Europe/Saratov": "Saratov Standard Time",
- "Europe/Simferopol": "Russian Standard Time",
- "Europe/Skopje": "Central European Standard Time",
- "Europe/Sofia": "FLE Standard Time",
- "Europe/Stockholm": "W. Europe Standard Time",
- "Europe/Tallinn": "FLE Standard Time",
- "Europe/Tirane": "Central Europe Standard Time",
- "Europe/Tiraspol": "E. Europe Standard Time",
- "Europe/Ulyanovsk": "Astrakhan Standard Time",
- "Europe/Uzhgorod": "FLE Standard Time",
- "Europe/Vaduz": "W. Europe Standard Time",
- "Europe/Vatican": "W. Europe Standard Time",
- "Europe/Vienna": "W. Europe Standard Time",
- "Europe/Vilnius": "FLE Standard Time",
- "Europe/Volgograd": "Volgograd Standard Time",
- "Europe/Warsaw": "Central European Standard Time",
- "Europe/Zagreb": "Central European Standard Time",
- "Europe/Zaporozhye": "FLE Standard Time",
- "Europe/Zurich": "W. Europe Standard Time",
- "GB": "GMT Standard Time",
- "GB-Eire": "GMT Standard Time",
- "GMT+0": "UTC",
- "GMT-0": "UTC",
- "GMT0": "UTC",
- "Greenwich": "UTC",
- "Hongkong": "China Standard Time",
- "Iceland": "Greenwich Standard Time",
- "Indian/Antananarivo": "E. Africa Standard Time",
- "Indian/Chagos": "Central Asia Standard Time",
- "Indian/Christmas": "SE Asia Standard Time",
- "Indian/Cocos": "Myanmar Standard Time",
- "Indian/Comoro": "E. Africa Standard Time",
- "Indian/Kerguelen": "West Asia Standard Time",
- "Indian/Mahe": "Mauritius Standard Time",
- "Indian/Maldives": "West Asia Standard Time",
- "Indian/Mauritius": "Mauritius Standard Time",
- "Indian/Mayotte": "E. Africa Standard Time",
- "Indian/Reunion": "Mauritius Standard Time",
- "Iran": "Iran Standard Time",
- "Israel": "Israel Standard Time",
- "Jamaica": "SA Pacific Standard Time",
- "Japan": "Tokyo Standard Time",
- "Kwajalein": "UTC+12",
- "Libya": "Libya Standard Time",
- "MST7MDT": "Mountain Standard Time",
- "Mexico/BajaNorte": "Pacific Standard Time (Mexico)",
- "Mexico/BajaSur": "Mountain Standard Time (Mexico)",
- "Mexico/General": "Central Standard Time (Mexico)",
- "NZ": "New Zealand Standard Time",
- "NZ-CHAT": "Chatham Islands Standard Time",
- "Navajo": "Mountain Standard Time",
- "PRC": "China Standard Time",
- "PST8PDT": "Pacific Standard Time",
- "Pacific/Apia": "Samoa Standard Time",
- "Pacific/Auckland": "New Zealand Standard Time",
- "Pacific/Bougainville": "Bougainville Standard Time",
- "Pacific/Chatham": "Chatham Islands Standard Time",
- "Pacific/Chuuk": "West Pacific Standard Time",
- "Pacific/Easter": "Easter Island Standard Time",
- "Pacific/Efate": "Central Pacific Standard Time",
- "Pacific/Enderbury": "UTC+13",
- "Pacific/Fakaofo": "UTC+13",
- "Pacific/Fiji": "Fiji Standard Time",
- "Pacific/Funafuti": "UTC+12",
- "Pacific/Galapagos": "Central America Standard Time",
- "Pacific/Gambier": "UTC-09",
- "Pacific/Guadalcanal": "Central Pacific Standard Time",
- "Pacific/Guam": "West Pacific Standard Time",
- "Pacific/Honolulu": "Hawaiian Standard Time",
- "Pacific/Johnston": "Hawaiian Standard Time",
- "Pacific/Kanton": "UTC+13",
- "Pacific/Kiritimati": "Line Islands Standard Time",
- "Pacific/Kosrae": "Central Pacific Standard Time",
- "Pacific/Kwajalein": "UTC+12",
- "Pacific/Majuro": "UTC+12",
- "Pacific/Marquesas": "Marquesas Standard Time",
- "Pacific/Midway": "UTC-11",
- "Pacific/Nauru": "UTC+12",
- "Pacific/Niue": "UTC-11",
- "Pacific/Norfolk": "Norfolk Standard Time",
- "Pacific/Noumea": "Central Pacific Standard Time",
- "Pacific/Pago_Pago": "UTC-11",
- "Pacific/Palau": "Tokyo Standard Time",
- "Pacific/Pitcairn": "UTC-08",
- "Pacific/Pohnpei": "Central Pacific Standard Time",
- "Pacific/Ponape": "Central Pacific Standard Time",
- "Pacific/Port_Moresby": "West Pacific Standard Time",
- "Pacific/Rarotonga": "Hawaiian Standard Time",
- "Pacific/Saipan": "West Pacific Standard Time",
- "Pacific/Samoa": "UTC-11",
- "Pacific/Tahiti": "Hawaiian Standard Time",
- "Pacific/Tarawa": "UTC+12",
- "Pacific/Tongatapu": "Tonga Standard Time",
- "Pacific/Truk": "West Pacific Standard Time",
- "Pacific/Wake": "UTC+12",
- "Pacific/Wallis": "UTC+12",
- "Pacific/Yap": "West Pacific Standard Time",
- "Poland": "Central European Standard Time",
- "Portugal": "GMT Standard Time",
- "ROC": "Taipei Standard Time",
- "ROK": "Korea Standard Time",
- "Singapore": "Singapore Standard Time",
- "Turkey": "Turkey Standard Time",
- "UCT": "UTC",
- "US/Alaska": "Alaskan Standard Time",
- "US/Aleutian": "Aleutian Standard Time",
- "US/Arizona": "US Mountain Standard Time",
- "US/Central": "Central Standard Time",
- "US/Eastern": "Eastern Standard Time",
- "US/Hawaii": "Hawaiian Standard Time",
- "US/Indiana-Starke": "Central Standard Time",
- "US/Michigan": "Eastern Standard Time",
- "US/Mountain": "Mountain Standard Time",
- "US/Pacific": "Pacific Standard Time",
- "US/Samoa": "UTC-11",
- "UTC": "UTC",
- "Universal": "UTC",
- "W-SU": "Russian Standard Time",
- "Zulu": "UTC",
-}
diff --git a/contrib/python/tzlocal/py3/ya.make b/contrib/python/tzlocal/py3/ya.make
deleted file mode 100644
index 4342acb95cd..00000000000
--- a/contrib/python/tzlocal/py3/ya.make
+++ /dev/null
@@ -1,35 +0,0 @@
-# Generated by devtools/yamaker (pypi).
-
-PY3_LIBRARY()
-
-VERSION(5.2)
-
-LICENSE(MIT)
-
-PEERDIR(
- contrib/python/tzdata
-)
-
-NO_LINT()
-
-NO_CHECK_IMPORTS(
- tzlocal.win32
-)
-
-PY_SRCS(
- TOP_LEVEL
- tzlocal/__init__.py
- tzlocal/unix.py
- tzlocal/utils.py
- tzlocal/win32.py
- tzlocal/windows_tz.py
-)
-
-RESOURCE_FILES(
- PREFIX contrib/python/tzlocal/py3/
- .dist-info/METADATA
- .dist-info/top_level.txt
- tzlocal/py.typed
-)
-
-END()
diff --git a/contrib/python/tzlocal/ya.make b/contrib/python/tzlocal/ya.make
deleted file mode 100644
index b29d8c6c043..00000000000
--- a/contrib/python/tzlocal/ya.make
+++ /dev/null
@@ -1,18 +0,0 @@
-PY23_LIBRARY()
-
-LICENSE(Service-Py23-Proxy)
-
-IF (PYTHON2)
- PEERDIR(contrib/python/tzlocal/py2)
-ELSE()
- PEERDIR(contrib/python/tzlocal/py3)
-ENDIF()
-
-NO_LINT()
-
-END()
-
-RECURSE(
- py2
- py3
-)
diff --git a/contrib/python/ydb/py3/.dist-info/METADATA b/contrib/python/ydb/py3/.dist-info/METADATA
index 71ddeb7b6fc..d921ac7f1ff 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.8.1
+Version: 3.9.0
Summary: YDB Python SDK
Home-page: http://github.com/ydb-platform/ydb-python-sdk
Author: Yandex LLC
@@ -18,6 +18,7 @@ Requires-Dist: grpcio >=1.42.0
Requires-Dist: packaging
Requires-Dist: protobuf <5.0.0,>=3.13.0
Requires-Dist: aiohttp <4
+Requires-Dist: pyjwt ==2.8.0
Provides-Extra: yc
Requires-Dist: yandexcloud ; extra == 'yc'
diff --git a/contrib/python/ydb/py3/ya.make b/contrib/python/ydb/py3/ya.make
index 24ba22bc0fe..b8611eae134 100644
--- a/contrib/python/ydb/py3/ya.make
+++ b/contrib/python/ydb/py3/ya.make
@@ -2,11 +2,12 @@
PY3_LIBRARY()
-VERSION(3.8.1)
+VERSION(3.9.0)
LICENSE(Apache-2.0)
PEERDIR(
+ contrib/python/PyJWT
contrib/python/aiohttp
contrib/python/grpcio
contrib/python/packaging
diff --git a/contrib/python/ydb/py3/ydb/aio/iam.py b/contrib/python/ydb/py3/ydb/aio/iam.py
index eab8faffe05..40622f8a9d9 100644
--- a/contrib/python/ydb/py3/ydb/aio/iam.py
+++ b/contrib/python/ydb/py3/ydb/aio/iam.py
@@ -5,15 +5,19 @@ import abc
import logging
from ydb.iam import auth
from .credentials import AbstractExpiringTokenCredentials
+from ydb import issues
logger = logging.getLogger(__name__)
try:
- from yandex.cloud.iam.v1 import iam_token_service_pb2_grpc
- from yandex.cloud.iam.v1 import iam_token_service_pb2
import jwt
except ImportError:
jwt = None
+
+try:
+ from yandex.cloud.iam.v1 import iam_token_service_pb2_grpc
+ from yandex.cloud.iam.v1 import iam_token_service_pb2
+except ImportError:
iam_token_service_pb2_grpc = None
iam_token_service_pb2 = None
@@ -55,6 +59,51 @@ class TokenServiceCredentials(AbstractExpiringTokenCredentials):
IamTokenCredentials = TokenServiceCredentials
+class OAuth2JwtTokenExchangeCredentials(AbstractExpiringTokenCredentials, auth.BaseJWTCredentials):
+ def __init__(
+ self,
+ token_exchange_url,
+ account_id,
+ access_key_id,
+ private_key,
+ algorithm,
+ token_service_url,
+ subject=None,
+ ):
+ super(OAuth2JwtTokenExchangeCredentials, self).__init__()
+ auth.BaseJWTCredentials.__init__(
+ self, account_id, access_key_id, private_key, algorithm, token_service_url, subject
+ )
+ assert aiohttp is not None, "Install aiohttp library to use OAuth 2.0 token exchange credentials provider"
+ self._token_exchange_url = token_exchange_url
+
+ async def _make_token_request(self):
+ params = {
+ "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
+ "requested_token_type": "urn:ietf:params:oauth:token-type:access_token",
+ "subject_token": self._get_jwt(),
+ "subject_token_type": "urn:ietf:params:oauth:token-type:jwt",
+ }
+ headers = {"Content-Type": "application/x-www-form-urlencoded"}
+
+ timeout = aiohttp.ClientTimeout(total=2)
+ async with aiohttp.ClientSession(timeout=timeout) as session:
+ async with session.post(self._token_exchange_url, data=params, headers=headers) as response:
+ if response.status == 403:
+ raise issues.Unauthenticated(await response.text())
+ if response.status >= 500:
+ raise issues.Unavailable(await response.text())
+ if response.status >= 400:
+ raise issues.BadRequest(await response.text())
+ if response.status != 200:
+ raise issues.Error(await response.text())
+
+ response_json = await response.json()
+ access_token = response_json["access_token"]
+ expires_in = response_json["expires_in"]
+ return {"access_token": access_token, "expires_in": expires_in}
+
+
class JWTIamCredentials(TokenServiceCredentials, auth.BaseJWTCredentials):
def __init__(
self,
@@ -65,16 +114,39 @@ class JWTIamCredentials(TokenServiceCredentials, auth.BaseJWTCredentials):
iam_channel_credentials=None,
):
TokenServiceCredentials.__init__(self, iam_endpoint, iam_channel_credentials)
- auth.BaseJWTCredentials.__init__(self, account_id, access_key_id, private_key)
+ auth.BaseJWTCredentials.__init__(
+ self,
+ account_id,
+ access_key_id,
+ private_key,
+ auth.YANDEX_CLOUD_JWT_ALGORITHM,
+ auth.YANDEX_CLOUD_IAM_TOKEN_SERVICE_URL,
+ )
def _get_token_request(self):
- return iam_token_service_pb2.CreateIamTokenRequest(
- jwt=auth.get_jwt(
- self._account_id,
- self._access_key_id,
- self._private_key,
- self._jwt_expiration_timeout,
- )
+ return iam_token_service_pb2.CreateIamTokenRequest(jwt=self._get_jwt())
+
+
+class NebiusJWTIamCredentials(OAuth2JwtTokenExchangeCredentials):
+ def __init__(
+ self,
+ account_id,
+ access_key_id,
+ private_key,
+ token_exchange_url=None,
+ ):
+ url = token_exchange_url
+ if url is None:
+ url = auth.NEBIUS_CLOUD_IAM_TOKEN_EXCHANGE_URL
+ OAuth2JwtTokenExchangeCredentials.__init__(
+ self,
+ url,
+ account_id,
+ access_key_id,
+ private_key,
+ auth.NEBIUS_CLOUD_JWT_ALGORITHM,
+ auth.NEBIUS_CLOUD_IAM_TOKEN_SERVICE_AUDIENCE,
+ account_id,
)
@@ -130,3 +202,20 @@ class ServiceAccountCredentials(JWTIamCredentials):
iam_endpoint,
iam_channel_credentials,
)
+
+
+class NebiusServiceAccountCredentials(NebiusJWTIamCredentials):
+ def __init__(
+ self,
+ service_account_id,
+ access_key_id,
+ private_key,
+ iam_endpoint=None,
+ iam_channel_credentials=None,
+ ):
+ super(NebiusServiceAccountCredentials, self).__init__(
+ service_account_id,
+ access_key_id,
+ private_key,
+ iam_endpoint,
+ )
diff --git a/contrib/python/ydb/py3/ydb/driver.py b/contrib/python/ydb/py3/ydb/driver.py
index 89109b9b575..16bba151540 100644
--- a/contrib/python/ydb/py3/ydb/driver.py
+++ b/contrib/python/ydb/py3/ydb/driver.py
@@ -38,6 +38,13 @@ def credentials_from_env_variables(tracer=None):
return ydb.iam.ServiceAccountCredentials.from_file(service_account_key_file)
+ nebius_service_account_key_file = os.getenv("YDB_NEBIUS_SERVICE_ACCOUNT_KEY_FILE_CREDENTIALS")
+ if nebius_service_account_key_file is not None:
+ ctx.trace({"credentials.nebius_service_account_key_file": True})
+ import ydb.iam
+
+ return ydb.iam.NebiusServiceAccountCredentials.from_file(nebius_service_account_key_file)
+
anonymous_credetials = os.getenv("YDB_ANONYMOUS_CREDENTIALS", "0") == "1"
if anonymous_credetials:
ctx.trace({"credentials.anonymous": True})
diff --git a/contrib/python/ydb/py3/ydb/iam/__init__.py b/contrib/python/ydb/py3/ydb/iam/__init__.py
index 7167efe13ee..cf835769dbf 100644
--- a/contrib/python/ydb/py3/ydb/iam/__init__.py
+++ b/contrib/python/ydb/py3/ydb/iam/__init__.py
@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from .auth import ServiceAccountCredentials # noqa
+from .auth import NebiusServiceAccountCredentials # noqa
from .auth import MetadataUrlCredentials # noqa
diff --git a/contrib/python/ydb/py3/ydb/iam/auth.py b/contrib/python/ydb/py3/ydb/iam/auth.py
index 82e7c9f6c8e..852c0c28bb3 100644
--- a/contrib/python/ydb/py3/ydb/iam/auth.py
+++ b/contrib/python/ydb/py3/ydb/iam/auth.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-from ydb import credentials, tracing
+from ydb import credentials, tracing, issues
import grpc
import time
import abc
@@ -8,11 +8,14 @@ import json
import os
try:
- from yandex.cloud.iam.v1 import iam_token_service_pb2_grpc
- from yandex.cloud.iam.v1 import iam_token_service_pb2
import jwt
except ImportError:
jwt = None
+
+try:
+ from yandex.cloud.iam.v1 import iam_token_service_pb2_grpc
+ from yandex.cloud.iam.v1 import iam_token_service_pb2
+except ImportError:
iam_token_service_pb2_grpc = None
iam_token_service_pb2 = None
@@ -23,22 +26,32 @@ except ImportError:
DEFAULT_METADATA_URL = "http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token"
+YANDEX_CLOUD_IAM_TOKEN_SERVICE_URL = "https://iam.api.cloud.yandex.net/iam/v1/tokens"
+NEBIUS_CLOUD_IAM_TOKEN_SERVICE_AUDIENCE = "token-service.iam.new.nebiuscloud.net"
+NEBIUS_CLOUD_IAM_TOKEN_EXCHANGE_URL = "https://auth.new.nebiuscloud.net/oauth2/token/exchange"
+YANDEX_CLOUD_JWT_ALGORITHM = "PS256"
+NEBIUS_CLOUD_JWT_ALGORITHM = "RS256"
-def get_jwt(account_id, access_key_id, private_key, jwt_expiration_timeout):
+
+def get_jwt(account_id, access_key_id, private_key, jwt_expiration_timeout, algorithm, token_service_url, subject=None):
+ assert jwt is not None, "Install pyjwt library to use jwt tokens"
now = time.time()
now_utc = datetime.utcfromtimestamp(now)
exp_utc = datetime.utcfromtimestamp(now + jwt_expiration_timeout)
+ payload = {
+ "iss": account_id,
+ "aud": token_service_url,
+ "iat": now_utc,
+ "exp": exp_utc,
+ }
+ if subject is not None:
+ payload["sub"] = subject
return jwt.encode(
key=private_key,
- algorithm="PS256",
- headers={"typ": "JWT", "alg": "PS256", "kid": access_key_id},
- payload={
- "iss": account_id,
- "aud": "https://iam.api.cloud.yandex.net/iam/v1/tokens",
- "iat": now_utc,
- "exp": exp_utc,
- },
+ algorithm=algorithm,
+ headers={"typ": "JWT", "alg": algorithm, "kid": access_key_id},
+ payload=payload,
)
@@ -73,12 +86,15 @@ class TokenServiceCredentials(credentials.AbstractExpiringTokenCredentials):
class BaseJWTCredentials(abc.ABC):
- def __init__(self, account_id, access_key_id, private_key):
+ def __init__(self, account_id, access_key_id, private_key, algorithm, token_service_url, subject=None):
self._account_id = account_id
self._jwt_expiration_timeout = 60.0 * 60
self._token_expiration_timeout = 120
self._access_key_id = access_key_id
self._private_key = private_key
+ self._algorithm = algorithm
+ self._token_service_url = token_service_url
+ self._subject = subject
def set_token_expiration_timeout(self, value):
self._token_expiration_timeout = value
@@ -99,6 +115,64 @@ class BaseJWTCredentials(abc.ABC):
iam_channel_credentials=iam_channel_credentials,
)
+ def _get_jwt(self):
+ return get_jwt(
+ self._account_id,
+ self._access_key_id,
+ self._private_key,
+ self._jwt_expiration_timeout,
+ self._algorithm,
+ self._token_service_url,
+ self._subject,
+ )
+
+
+class OAuth2JwtTokenExchangeCredentials(credentials.AbstractExpiringTokenCredentials, BaseJWTCredentials):
+ def __init__(
+ self,
+ token_exchange_url,
+ account_id,
+ access_key_id,
+ private_key,
+ algorithm,
+ token_service_url,
+ subject=None,
+ tracer=None,
+ ):
+ BaseJWTCredentials.__init__(self, account_id, access_key_id, private_key, algorithm, token_service_url, subject)
+ super(OAuth2JwtTokenExchangeCredentials, self).__init__(tracer)
+ assert requests is not None, "Install requests library to use OAuth 2.0 token exchange credentials provider"
+ self._token_exchange_url = token_exchange_url
+
+ def _process_response_status_code(self, response):
+ if response.status_code == 403:
+ raise issues.Unauthenticated(response.content)
+ if response.status_code >= 500:
+ raise issues.Unavailable(response.content)
+ if response.status_code >= 400:
+ raise issues.BadRequest(response.content)
+ if response.status_code != 200:
+ raise issues.Error(response.content)
+
+ def _process_response(self, response):
+ self._process_response_status_code(response)
+ response_json = json.loads(response.content)
+ access_token = response_json["access_token"]
+ expires_in = response_json["expires_in"]
+ return {"access_token": access_token, "expires_in": expires_in}
+
+ @tracing.with_trace()
+ def _make_token_request(self):
+ params = {
+ "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
+ "requested_token_type": "urn:ietf:params:oauth:token-type:access_token",
+ "subject_token": self._get_jwt(),
+ "subject_token_type": "urn:ietf:params:oauth:token-type:jwt",
+ }
+ headers = {"Content-Type": "application/x-www-form-urlencoded"}
+ response = requests.post(self._token_exchange_url, data=params, headers=headers)
+ return self._process_response(response)
+
class JWTIamCredentials(TokenServiceCredentials, BaseJWTCredentials):
def __init__(
@@ -110,16 +184,34 @@ class JWTIamCredentials(TokenServiceCredentials, BaseJWTCredentials):
iam_channel_credentials=None,
):
TokenServiceCredentials.__init__(self, iam_endpoint, iam_channel_credentials)
- BaseJWTCredentials.__init__(self, account_id, access_key_id, private_key)
+ BaseJWTCredentials.__init__(
+ self, account_id, access_key_id, private_key, YANDEX_CLOUD_JWT_ALGORITHM, YANDEX_CLOUD_IAM_TOKEN_SERVICE_URL
+ )
def _get_token_request(self):
- return self._iam_token_service_pb2.CreateIamTokenRequest(
- jwt=get_jwt(
- self._account_id,
- self._access_key_id,
- self._private_key,
- self._jwt_expiration_timeout,
- )
+ return self._iam_token_service_pb2.CreateIamTokenRequest(jwt=self._get_jwt())
+
+
+class NebiusJWTIamCredentials(OAuth2JwtTokenExchangeCredentials):
+ def __init__(
+ self,
+ account_id,
+ access_key_id,
+ private_key,
+ token_exchange_url=None,
+ ):
+ url = token_exchange_url
+ if url is None:
+ url = NEBIUS_CLOUD_IAM_TOKEN_EXCHANGE_URL
+ OAuth2JwtTokenExchangeCredentials.__init__(
+ self,
+ url,
+ account_id,
+ access_key_id,
+ private_key,
+ NEBIUS_CLOUD_JWT_ALGORITHM,
+ NEBIUS_CLOUD_IAM_TOKEN_SERVICE_AUDIENCE,
+ account_id,
)
@@ -176,3 +268,20 @@ class ServiceAccountCredentials(JWTIamCredentials):
iam_endpoint,
iam_channel_credentials,
)
+
+
+class NebiusServiceAccountCredentials(NebiusJWTIamCredentials):
+ def __init__(
+ self,
+ service_account_id,
+ access_key_id,
+ private_key,
+ iam_endpoint=None,
+ iam_channel_credentials=None,
+ ):
+ super(NebiusServiceAccountCredentials, self).__init__(
+ service_account_id,
+ access_key_id,
+ private_key,
+ iam_endpoint,
+ )
diff --git a/contrib/python/ydb/py3/ydb/ydb_version.py b/contrib/python/ydb/py3/ydb/ydb_version.py
index a464e6dc077..10baa2a41a3 100644
--- a/contrib/python/ydb/py3/ydb/ydb_version.py
+++ b/contrib/python/ydb/py3/ydb/ydb_version.py
@@ -1 +1 @@
-VERSION = "3.8.1"
+VERSION = "3.9.0"
diff --git a/contrib/python/ydb/ya.make b/contrib/python/ydb/ya.make
deleted file mode 100644
index 1886a29d3d8..00000000000
--- a/contrib/python/ydb/ya.make
+++ /dev/null
@@ -1,23 +0,0 @@
-PY23_LIBRARY()
-
-LICENSE(Service-Py23-Proxy)
-
-IF (PYTHON2)
- PEERDIR(contrib/python/ydb/py2)
-ELSE()
- PEERDIR(contrib/python/ydb/py3)
-ENDIF()
-
-PEERDIR(
- contrib/ydb/public/api/grpc
- contrib/ydb/public/api/grpc/draft
-)
-
-NO_LINT()
-
-END()
-
-RECURSE(
- py2
- py3
-) \ No newline at end of file