diff options
| author | AlexSm <[email protected]> | 2024-04-08 16:46:46 +0200 |
|---|---|---|
| committer | GitHub <[email protected]> | 2024-04-08 16:46:46 +0200 |
| commit | 5ebf1bc5956c43c050a7b08d9c266f35a10e2e71 (patch) | |
| tree | 39b3697735fab3b6872fa0c94875afc4c1938c29 /contrib/python | |
| parent | ba3d69df82d1e10f6bbac0f98da0217f2287692f (diff) | |
| parent | 2ccadbed5e49091384169de142a3829bd7c18f4f (diff) | |
Merge pull request #3543 from ydb-platform/mergelibs-240408-0819
Library import 240408-0819
Diffstat (limited to 'contrib/python')
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 |
